diff --git a/dens-transactions/package-lock.json b/dens-transactions/package-lock.json index 37ac293..5ecf66a 100644 --- a/dens-transactions/package-lock.json +++ b/dens-transactions/package-lock.json @@ -20,6 +20,7 @@ "lbr-plutus": "file:.extra-dependencies/lbr-plutus", "lbr-prelude": "file:.extra-dependencies/lbr-prelude", "lucid-cardano": "^0.10.7", + "node-fetch": "^3.3.2", "plutus-ledger-api": "file:.extra-dependencies/plutus-ledger-api", "prelude": "file:.extra-dependencies/prelude" }, diff --git a/dens-transactions/package.json b/dens-transactions/package.json index 95fc939..c1c498f 100644 --- a/dens-transactions/package.json +++ b/dens-transactions/package.json @@ -38,6 +38,7 @@ "lbr-plutus": "file:.extra-dependencies/lbr-plutus", "lbr-prelude": "file:.extra-dependencies/lbr-prelude", "lucid-cardano": "^0.10.7", + "node-fetch": "^3.3.2", "plutus-ledger-api": "file:.extra-dependencies/plutus-ledger-api", "prelude": "file:.extra-dependencies/prelude" } diff --git a/dens-transactions/src/DensTransactions/DensTransactions.ts b/dens-transactions/src/DensTransactions/DensTransactions.ts index 5396538..79986c2 100644 --- a/dens-transactions/src/DensTransactions/DensTransactions.ts +++ b/dens-transactions/src/DensTransactions/DensTransactions.ts @@ -1,59 +1,37 @@ import * as L from "lucid-cardano"; import { DensKey, - DensValue, Protocol, RecordDatum, SetDatum, - SetInsert, } from "lbf-dens/LambdaBuffers/Dens.mjs"; import { IsPlutusData } from "lbr-plutus/PlutusData.js"; -import * as Pla from "plutus-ledger-api/PlutusData.js"; -import * as csl from "@emurgo/cardano-serialization-lib-nodejs"; -import { fromJust } from "prelude"; -import { scriptHashFromBytes } from "plutus-ledger-api/V1.js"; - -type DeNSParams = { - setValidator: L.SpendingValidator; - recordValidator: L.SpendingValidator; - setElemIDPolicy: L.MintingPolicy; - elemIDPolicy: L.MintingPolicy; - protocolPolicy: L.MintingPolicy; -}; - -const mkLBScriptHash = (script: L.SpendingValidator) => { - return fromJust( - scriptHashFromBytes(L.fromHex(L.applyDoubleCborEncoding(script.script))), - ); -}; +import * as Utils from "./Utils.js" -const initializeDeNS = async ( +export const initializeDeNS = async ( lucid: L.Lucid, - params: DeNSParams, + params: Utils.DeNSParams, ): Promise => { const builder = new L.Tx(lucid); const utils = new L.Utils(lucid); - const initialSetDatum: SetDatum = undefined; - const initialSetDatumPD = IsPlutusData[SetDatum].toData(initialSetDatum); - const initialSetDatumCSL = (toCslPlutusData(initialSetDatumPD)).to_hex(); + const initialSetDatumPD = IsPlutusData[SetDatum].toData(Utils.initialSetDatum); + const initialSetDatumCSL = (Utils.toCslPlutusData(initialSetDatumPD)).to_hex(); const initialSetDatumDatum: L.OutputData = { inline: initialSetDatumCSL }; const protocolDatumRaw: Protocol = { - elementIdMintingPolicy: mkLBScriptHash(params.elemIDPolicy), - setElemMintingPolicy: mkLBScriptHash(params.setElemIDPolicy), - setValidator: mkLBScriptHash(params.setValidator), - recordsValidator: mkLBScriptHash(params.recordValidator), + elementIdMintingPolicy: Utils.mkLBScriptHash(params.elemIDPolicy), + setElemMintingPolicy: Utils.mkLBScriptHash(params.setElemIDPolicy), + setValidator: Utils.mkLBScriptHash(params.setValidator), + recordsValidator: Utils.mkLBScriptHash(params.recordValidator), }; const protocolDatum: L.OutputData = { - inline: toCslPlutusData(IsPlutusData[Protocol].toData(protocolDatumRaw)) + inline: Utils.toCslPlutusData(IsPlutusData[Protocol].toData(protocolDatumRaw)) .to_hex(), }; - const findProtocolOut: () => Promise = undefined; - - const protocolOut = await findProtocolOut(); + const protocolOut = await Utils.findProtocolOut(lucid); // Mint one protocol token const protocolPolicyID = utils.mintingPolicyToId(params.protocolPolicy); @@ -78,9 +56,9 @@ const initializeDeNS = async ( .collectFrom([protocolOut]); }; -const registerDomain = async ( +export const registerDomain = async ( lucid: L.Lucid, - params: DeNSParams, + params: Utils.DeNSParams, domain: string, ): Promise => { const builder = new L.Tx(lucid); @@ -90,21 +68,33 @@ const registerDomain = async ( [utils.mintingPolicyToId(params.setElemIDPolicy)]: BigInt(1), }; - const newSetDatumL: L.OutputData = undefined; - const newSetDatumR: L.OutputData = undefined; + const setDatumResponse = await Utils.findOldSetDatum(lucid,domain); - const elemIDPolicy = utils.mintingPolicyToId(params.elemIDPolicy); - const elemIDAssetClass: L.Unit = elemIDPolicy + L.fromText(domain); + const oldSetDatum = setDatumResponse.setDatum; + const oldSetDatumUtxo = setDatumResponse.setDatumUTxO; - const oneElemIDToken = { [elemIDAssetClass]: BigInt(1) }; + const k: DensKey = oldSetDatum.key; + const nxt: DensKey = oldSetDatum.next; - const findOldSetDatumUTxO: () => Promise = undefined; + const sdl: SetDatum = {key: k, + next: {densName: Buffer.from(domain,'utf8'), densClass: BigInt(0)}, + ownerApproval: Utils.emptyCS + }; + const sdr: SetDatum = {key: {densName: Buffer.from(domain,'utf8'), densClass: BigInt(0)}, + next: nxt, + ownerApproval: Utils.emptyCS + }; - const oldSetDatumUTxO = await findOldSetDatumUTxO(); - const findProtocolOut: () => Promise = undefined; + const newSetDatumL: L.OutputData = {inline: Utils.toCslPlutusData(IsPlutusData[SetDatum].toData(sdl)).to_hex()}; + const newSetDatumR: L.OutputData = {inline: Utils.toCslPlutusData(IsPlutusData[SetDatum].toData(sdr)).to_hex()}; - const protocolOut = await findProtocolOut(); + const elemIDPolicy = utils.mintingPolicyToId(params.elemIDPolicy); + const elemIDAssetClass: L.Unit = elemIDPolicy + L.fromText(domain); + + const oneElemIDToken = { [elemIDAssetClass]: BigInt(1) }; + + const protocolOut = await Utils.findProtocolOut(lucid); const setValidatorAddr = utils.validatorToAddress(params.setValidator); @@ -112,14 +102,14 @@ const registerDomain = async ( .mintAssets(oneSetElemToken) .mintAssets(oneElemIDToken) .readFrom([protocolOut]) - .collectFrom([oldSetDatumUTxO]) + .collectFrom([oldSetDatumUtxo]) .payToAddressWithData(setValidatorAddr, newSetDatumL, oneSetElemToken) .payToAddressWithData(setValidatorAddr, newSetDatumR, oneSetElemToken); }; -const updateRecord = async ( +export const updateRecord = async ( lucid: L.Lucid, - params: DeNSParams, + params: Utils.DeNSParams, user: L.Address, domain: string, record: RecordDatum, @@ -134,16 +124,13 @@ const updateRecord = async ( const elemIDToken = { [elemIDAssetClass]: BigInt(1) }; - const findProtocolOut: () => Promise = undefined; - - const protocolOut = await findProtocolOut(); + const protocolOut = await Utils.findProtocolOut(lucid); - const findElemIDUTxO: () => Promise = undefined; - const elemIDUTxO: L.UTxO = await findElemIDUTxO(); + const elemIDUTxO: L.UTxO = await Utils.findElemIDUTxO(elemIDAssetClass,lucid); const recordDatum: L.OutputData = { - inline: toCslPlutusData(IsPlutusData[RecordDatum].toData(record)).to_hex(), + inline: Utils.toCslPlutusData(IsPlutusData[RecordDatum].toData(record)).to_hex(), }; return builder @@ -155,45 +142,3 @@ const updateRecord = async ( .payToAddress(user, elemIDToken); }; -export function toCslPlutusData( - plutusData: Pla.PlutusData, -): csl.PlutusData { - switch (plutusData.name) { - case "Integer": - return csl.PlutusData.new_integer( - csl.BigInt.from_str(plutusData.fields.toString()), - ); - case "Bytes": - return csl.PlutusData.new_bytes(plutusData.fields); - case "List": - return csl.PlutusData.new_list( - plaPdListToCslPlutusList(plutusData.fields), - ); - case "Constr": - return csl.PlutusData.new_constr_plutus_data( - csl.ConstrPlutusData.new( - csl.BigNum.from_str(plutusData.fields[0].toString()), - plaPdListToCslPlutusList(plutusData.fields[1]), - ), - ); - case "Map": { - const plutusMap = csl.PlutusMap.new(); - for (const elem of plutusData.fields) { - plutusMap.insert( - toCslPlutusData(elem[0]), - toCslPlutusData(elem[1]), - ); - } - return csl.PlutusData.new_map(plutusMap); - } - } -} - -function plaPdListToCslPlutusList(list: Pla.PlutusData[]): csl.PlutusList { - const result = csl.PlutusList.new(); - - for (const elem of list) { - result.add(toCslPlutusData(elem)); - } - return result; -} diff --git a/dens-transactions/src/DensTransactions/Utils.ts b/dens-transactions/src/DensTransactions/Utils.ts new file mode 100644 index 0000000..7977354 --- /dev/null +++ b/dens-transactions/src/DensTransactions/Utils.ts @@ -0,0 +1,236 @@ +import * as L from "lucid-cardano"; + +import { + DensKey, + SetDatum, +} from "lbf-dens/LambdaBuffers/Dens.mjs"; +import { IsPlutusData } from "lbr-plutus/PlutusData.js"; +import * as Pla from "plutus-ledger-api/PlutusData.js"; +import * as csl from "@emurgo/cardano-serialization-lib-nodejs"; +import { fromJust } from "prelude"; +import { currencySymbolFromBytes, scriptHashFromBytes } from "plutus-ledger-api/V1.js"; +import fetch from 'node-fetch' + +const serverBaseUrl: string = "www.test.com"; + +export const mkDensKey = (domain: string): DensKey => { + return {densName: Buffer.from(domain,'utf8'), densClass: BigInt(0)} +} + +type ProtocolResponseBody = { + txOutRef: {txOutRef: string, txOutRefIx: number}, + protocol: { + elementIdMintingPolicy: string, + setElemMintingPolicy: string, + setValidator: string, + recordsValidator: string + } +} + +type ProtocolResponse = { + name: string, + fields: Array +} + +export const unsafeCurrSymb = (x: string) => { + return fromJust(currencySymbolFromBytes(Buffer.from(x,'utf8'))) +} + +export const emptyCS = unsafeCurrSymb(""); + + +export const findProtocolOut: (lucid: L.Lucid) => Promise = async (lucid: L.Lucid) => { + const response = await fetch(serverBaseUrl + '/api/protocol-utxo', { + method: 'post', + body: JSON.stringify({}), + headers: {'Content-Type': 'application/json'} + }); + + const data = await response.json(); + + const protocolResponse = data as ProtocolResponse; + + const txOutRef = protocolResponse.fields[0].txOutRef.txOutRef; + const txOutRefIx = protocolResponse.fields[0].txOutRef.txOutRefIx; + + const utxos = await lucid.utxosByOutRef([{txHash: txOutRef, outputIndex: txOutRefIx}]) + + return utxos[0] +}; + + +type SetDatumQueryResult = {setDatumUTxO: L.UTxO, setDatum: SetDatum} + +type SetDatumResponseBody = { + name: string, + pointer: { + currency_symbol: string, + token_name: string + }, + txOutRef: { + txOutRef: string, + txOutRefIx: number + } + } + +type SetDatumResponse = {name: string, fields: Array} + +export const findOldSetDatum: (lucid: L.Lucid, domain: string) => Promise = async (lucid: L.Lucid, domain: string) => { + const hexDomain = Buffer.from(domain,'utf8').toString('hex'); + + const response = await fetch(serverBaseUrl + '/api/protocol-utxo', { + method: 'post', + body: JSON.stringify({name: hexDomain}), + headers: {'Content-Type': 'application/json'} + }); + + const data = await response.json() + + const setDatumResponse = data as SetDatumResponse; + + const txOutRef = setDatumResponse.fields[0].txOutRef.txOutRef; + const txOutRefIx = setDatumResponse.fields[0].txOutRef.txOutRefIx; + + const utxos = await lucid.utxosByOutRef([{txHash: txOutRef, outputIndex: txOutRefIx}]) + + const setDatumUtxo = utxos[0]; + + const setDatum = IsPlutusData[SetDatum].fromData(toPlaPlutusData(L.C.PlutusData.from_bytes(L.fromHex(setDatumUtxo.datum)))); + + return {setDatumUTxO: setDatumUtxo, setDatum: setDatum} +}; + +// TODO: Figure out if lucid exposes any utilities for filtering wallet UTxOs. +export const findElemIDUTxO = async (assetClass: string, lucid: L.Lucid): Promise => { + const walletUtxos = await lucid.wallet.getUtxos(); + + // TODO: Comically unsafe, do better error handling + return walletUtxos.filter(x => x.assets[assetClass] >= 1)[0] +} + + + + +export type DeNSParams = { + setValidator: L.SpendingValidator; + recordValidator: L.SpendingValidator; + setElemIDPolicy: L.MintingPolicy; + elemIDPolicy: L.MintingPolicy; + protocolPolicy: L.MintingPolicy; +}; + + +// Hopefully I'm getting the encoding right... +export const initialSetDatum: SetDatum = { + key: mkDensKey(""), + next: mkDensKey("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"), + ownerApproval: emptyCS +} + +export const mkLBScriptHash = (script: L.SpendingValidator) => { + return fromJust( + scriptHashFromBytes(L.fromHex(L.applyDoubleCborEncoding(script.script))), + ); +}; + +export function toCslPlutusData( + plutusData: Pla.PlutusData, +): csl.PlutusData { + switch (plutusData.name) { + case "Integer": + return csl.PlutusData.new_integer( + csl.BigInt.from_str(plutusData.fields.toString()), + ); + case "Bytes": + return csl.PlutusData.new_bytes(plutusData.fields); + case "List": + return csl.PlutusData.new_list( + plaPdListToCslPlutusList(plutusData.fields), + ); + case "Constr": + return csl.PlutusData.new_constr_plutus_data( + csl.ConstrPlutusData.new( + csl.BigNum.from_str(plutusData.fields[0].toString()), + plaPdListToCslPlutusList(plutusData.fields[1]), + ), + ); + case "Map": { + const plutusMap = csl.PlutusMap.new(); + for (const elem of plutusData.fields) { + plutusMap.insert( + toCslPlutusData(elem[0]), + toCslPlutusData(elem[1]), + ); + } + return csl.PlutusData.new_map(plutusMap); + } + } +} + +function plaPdListToCslPlutusList(list: Pla.PlutusData[]): csl.PlutusList { + const result = csl.PlutusList.new(); + + for (const elem of list) { + result.add(toCslPlutusData(elem)); + } + return result; +} + +export function toPlaPlutusData( + plutusData: L.C.PlutusData, +): Pla.PlutusData { + const constr = plutusData.as_constr_plutus_data(); + const map = plutusData.as_map(); + const list = plutusData.as_list(); + const integer = plutusData.as_integer(); + const bytes = plutusData.as_bytes(); + + if (constr !== undefined) { + const alternative = constr.alternative(); + const data = constr.data(); + + return { + name: "Constr", + fields: [BigInt(alternative.to_str()), cslPlutusListToPlaPdList(data)], + }; + } + + if (map !== undefined) { + const keys = map.keys(); + const result: [Pla.PlutusData, Pla.PlutusData][] = []; + + for (let i = 0; i < keys.len(); ++i) { + const k = keys.get(i); + result.push([ + toPlaPlutusData(k), + toPlaPlutusData(map.get(k)!), + ]); + } + + return { name: `Map`, fields: result }; + } + + if (list !== undefined) { + return { name: `List`, fields: cslPlutusListToPlaPdList(list) }; + } + + if (integer !== undefined) { + return { name: `Integer`, fields: BigInt(integer.to_str()) }; + } + + if (bytes !== undefined) { + return { name: `Bytes`, fields: bytes }; + } + + throw new Error( + "Internal error when converting cardano-serialization-lib PlutusData to plutus-ledger-api PlutusData", + ); +} + +function cslPlutusListToPlaPdList(list: L.C.PlutusList): Pla.PlutusData[] { + const result = []; + for (let i = 0; i < list.len(); ++i) { + result.push(toPlaPlutusData(list.get(i))); + } + return result; +}