From 2410531239f686e3a8e1f5d7606876c680f7dfb4 Mon Sep 17 00:00:00 2001 From: tidvn Date: Mon, 18 Nov 2024 11:59:09 +0700 Subject: [PATCH] feat: update compiler version, add mint and store validators, and enhance transaction builder with reference scripts --- contract/aiken.toml | 2 +- contract/script/adapters/mesh.adapter.ts | 1 + contract/script/constants/index.ts | 17 +-- contract/script/txbuilder/cip68.txbuilder.ts | 37 ++++++- contract/tests/cip68.test.ts | 83 ++++++++------- contract/validators/mint.ak | 103 +++++++++++++++++++ contract/validators/store.ak | 55 ++++++++++ 7 files changed, 250 insertions(+), 48 deletions(-) create mode 100644 contract/validators/mint.ak create mode 100644 contract/validators/store.ak diff --git a/contract/aiken.toml b/contract/aiken.toml index 564a76f7..46ddc800 100644 --- a/contract/aiken.toml +++ b/contract/aiken.toml @@ -1,6 +1,6 @@ name = "cardano2vn/cip68generator" version = "0.0.0" -compiler = "v1.1.6" +compiler = "v1.1.0" plutus = "v3" license = "Apache-2.0" description = "Aiken contracts for project 'cardano2vn/cip68generator'" diff --git a/contract/script/adapters/mesh.adapter.ts b/contract/script/adapters/mesh.adapter.ts index 0ba1a6e4..3207dc7c 100644 --- a/contract/script/adapters/mesh.adapter.ts +++ b/contract/script/adapters/mesh.adapter.ts @@ -34,6 +34,7 @@ export class MeshAdapter { const utxos = await this.wallet.getUtxos(); const collaterals = await this.wallet.getCollateral(); const walletAddress = await this.wallet.getChangeAddress(); + console.log(this.wallet.getUtxos()); if (!utxos || utxos.length === 0) throw new Error("No UTXOs found in getWalletForTx method."); diff --git a/contract/script/constants/index.ts b/contract/script/constants/index.ts index 0a9b152d..ec55ed41 100644 --- a/contract/script/constants/index.ts +++ b/contract/script/constants/index.ts @@ -1,12 +1,15 @@ export const EXCHANGE_FEE_ADDRESS = process.env.EXCHANGE_FEE_ADDRESS || ""; -export const REFERENCE_SCRIPT_ADDRESS = - process.env.REFERENCE_SCRIPT_ADDRESS || ""; - -export const REFERENCE_SCRIPT_HASH = process.env.REFERENCE_SCRIPT_HASH || ""; - +export const MINT_REFERENCE_SCRIPT_ADDRESS = + process.env.MINT_REFERENCE_SCRIPT_ADDRESS || ""; +export const STORE_REFERENCE_SCRIPT_ADDRESS = + process.env.STORE_REFERENCE_SCRIPT_ADDRESS || ""; +export const MINT_REFERENCE_SCRIPT_HASH = + process.env.MINT_REFERENCE_SCRIPT_HASH || ""; +export const STORE_REFERENCE_SCRIPT_HASH = + process.env.STORE_REFERENCE_SCRIPT_HASH || ""; export const EXCHANGE_FEE_PRICE = process.env.EXCHANGE_FEE_PRICE || ""; export const title = { - mint: "cip68generator.cip68generator.mint", - store: "cip68generator.cip68generator.spend", + mint: "mint.mint.mint", + store: "store.store.spend", }; diff --git a/contract/script/txbuilder/cip68.txbuilder.ts b/contract/script/txbuilder/cip68.txbuilder.ts index 419bef0a..90618efc 100644 --- a/contract/script/txbuilder/cip68.txbuilder.ts +++ b/contract/script/txbuilder/cip68.txbuilder.ts @@ -18,8 +18,10 @@ import { MeshAdapter } from "../adapters/mesh.adapter"; import plutus from "../../plutus.json"; import { EXCHANGE_FEE_ADDRESS, - REFERENCE_SCRIPT_HASH, - REFERENCE_SCRIPT_ADDRESS, + MINT_REFERENCE_SCRIPT_HASH, + STORE_REFERENCE_SCRIPT_HASH, + MINT_REFERENCE_SCRIPT_ADDRESS, + STORE_REFERENCE_SCRIPT_ADDRESS, title, } from "../constants"; import { Plutus } from "../types"; @@ -287,7 +289,7 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract { /** * @description Create reference script for mint transaction */ - createReferenceScript = async () => { + createReferenceScriptMint = async () => { const { walletAddress, utxos, collateral } = await this.getWalletForTx(); const unsignedTx = await this.meshTxBuilder @@ -297,10 +299,10 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract { collateral.output.amount, collateral.output.address, ) - .txOut(REFERENCE_SCRIPT_ADDRESS, [ + .txOut(MINT_REFERENCE_SCRIPT_ADDRESS, [ { unit: "lovelace", - quantity: "15000000", + quantity: "5000000", }, ]) @@ -317,4 +319,29 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract { return unsignedTx.complete(); }; + + createReferenceScriptStore = async () => { + const { walletAddress, utxos, collateral } = await this.getWalletForTx(); + const unsignedTx = await this.meshTxBuilder + .txIn(collateral.input.txHash, collateral.input.outputIndex) + .txOut(STORE_REFERENCE_SCRIPT_ADDRESS, [ + { + unit: "lovelace", + quantity: "7000000", + }, + ]) + + .txOutReferenceScript(this.storeScriptCbor, "V3") + .txOutInlineDatumValue("") + .changeAddress(walletAddress) + .selectUtxosFrom(utxos) + .txInCollateral( + collateral.input.txHash, + collateral.input.outputIndex, + collateral.output.amount, + collateral.output.address, + ); + + return unsignedTx.complete(); + }; } diff --git a/contract/tests/cip68.test.ts b/contract/tests/cip68.test.ts index 9b2ebd3a..f4a007a4 100644 --- a/contract/tests/cip68.test.ts +++ b/contract/tests/cip68.test.ts @@ -34,32 +34,32 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () { }); }); - // test("Mint", async function () { - // const cip68Contract: Cip68Contract = new Cip68Contract({ - // fetcher: blockfrostProvider, - // wallet: wallet, - // meshTxBuilder: meshTxBuilder, - // }); + test("Mint", async function () { + const cip68Contract: Cip68Contract = new Cip68Contract({ + fetcher: blockfrostProvider, + wallet: wallet, + meshTxBuilder: meshTxBuilder, + }); - // const unsignedTx: string = await cip68Contract.mint({ - // assetName: "CIP68 Generators.", - // metadata: { - // name: "CIP68 Generators", - // image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua", - // mediaType: "image/jpg", - // description: "Open source dynamic assets (Token/NFT) generator (CIP68)", - // author: deserializeAddress(await wallet.getChangeAddress()).pubKeyHash, - // }, - // quantity: "1", - // }); - // const signedTx = await wallet.signTx(unsignedTx, true); - // const txHash = await wallet.submitTx(signedTx); - // console.log(txHash); - // txHashTemp = txHash; - // blockfrostProvider.onTxConfirmed(txHash, () => { - // expect(txHash.length).toBe(64); - // }); - // }); + const unsignedTx: string = await cip68Contract.mint({ + assetName: "CIP68 Generators.", + metadata: { + name: "CIP68 Generators", + image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua", + mediaType: "image/jpg", + description: "Open source dynamic assets (Token/NFT) generator (CIP68)", + author: deserializeAddress(await wallet.getChangeAddress()).pubKeyHash, + }, + quantity: "1", + }); + const signedTx = await wallet.signTx(unsignedTx, true); + const txHash = await wallet.submitTx(signedTx); + console.log(txHash); + txHashTemp = txHash; + blockfrostProvider.onTxConfirmed(txHash, () => { + expect(txHash.length).toBe(64); + }); + }); // test("Burn", async function () { // const cip68Contract: Cip68Contract = new Cip68Contract({ @@ -119,16 +119,29 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () { // expect(txHash.length).toBe(64); // }); - // test(" Reference Script", async function () { - // const cip68Contract: Cip68Contract = new Cip68Contract({ - // fetcher: blockfrostProvider, - // wallet: wallet, - // meshTxBuilder: meshTxBuilder, + // test('Mint Reference Script', async function () { + // const cip68Contract: Cip68Contract = new Cip68Contract({ + // fetcher: blockfrostProvider, + // wallet: wallet, + // meshTxBuilder: meshTxBuilder, + // }); + // const unsignedTx: string = await cip68Contract.createReferenceScriptMint(); + // const signedTx = await wallet.signTx(unsignedTx, true); + // const txHash = await wallet.submitTx(signedTx); + // console.log(txHash); + // expect(txHash.length).toBe(64); // }); - // const unsignedTx: string = await cip68Contract.createReferenceScript(); - // const signedTx = await wallet.signTx(unsignedTx, true); - // const txHash = await wallet.submitTx(signedTx); - // console.log(txHash); - // expect(txHash.length).toBe(64); + + // test('Store Reference Script', async function () { + // const cip68Contract: Cip68Contract = new Cip68Contract({ + // fetcher: blockfrostProvider, + // wallet: wallet, + // meshTxBuilder: meshTxBuilder, + // }); + // const unsignedTx: string = await cip68Contract.createReferenceScriptStore(); + // const signedTx = await wallet.signTx(unsignedTx, true); + // const txHash = await wallet.submitTx(signedTx); + // console.log(txHash); + // expect(txHash.length).toBe(64); // }); }); diff --git a/contract/validators/mint.ak b/contract/validators/mint.ak new file mode 100644 index 00000000..f4d653b8 --- /dev/null +++ b/contract/validators/mint.ak @@ -0,0 +1,103 @@ +use aiken/collection/list +use aiken/crypto.{ScriptHash, VerificationKeyHash} +use aiken/primitive/bytearray +use cardano/address +use cardano/assets.{PolicyId, without_lovelace} +use cardano/minting +use cardano/transaction.{Transaction} +use cip68generator/types.{Burn, Mint, MintRedeemer} +use cip68generator/utils +use types/cip68 +use validation/find + +// Validator: Mint +// Description: Valodator use mint and burn assets dynamic assets (Token/NFT) generator (CIP68) +// Parameters: exchange: Exchange wallet address, exchange_fee: Minimum price sent to the exchange, store: Store address wallet, + +// Mint Redeemer +// sign_by_author: +validator mint( + exchange: VerificationKeyHash, + exchange_fee: Int, + store: ScriptHash, +) { + mint(redeemer: MintRedeemer, policy_id: PolicyId, transaction: Transaction) { + True + // let Transaction { outputs, extra_signatories, mint, .. } = transaction + // let mint_flatten = + // mint + // |> without_lovelace() + // |> assets.flatten() + + // let reference_token_option = + // utils.token_prefix(mint_flatten, cip68.prefix_100) + // let user_token_option = utils.token_prefix(mint_flatten, cip68.prefix_222) + // let amount_mint_token: Int = list.length(mint_flatten) + // let exchange_address = address.from_verification_key(exchange) + // let output_utxo_exchange = + // utils.find_output(outputs, exchange_fee, exchange_address) + // let check_none_token = + // utils.check_none_token(user_token_option, reference_token_option) + + // when check_none_token is { + // True -> + // when (reference_token_option, user_token_option) is { + // (Some(reference_token), Some(user_token)) -> { + // let reference_value = + // assets.from_asset(policy_id, reference_token, 1) + // // let user_value = + // // assets.from_asset(policy_id, user_token, amount_mint_token - 1) + // let store_address = address.from_script(store) + // let output_utxo_store = + // find.output_by_addr_value(outputs, store_address, reference_value) + + // when redeemer is { + // Mint -> and { + // amount_mint_token >= 2, + // minting.exact(mint_flatten, policy_id, reference_token, 1)?, + // minting.exact( + // mint_flatten, + // policy_id, + // user_token, + // amount_mint_token - 1, + // )?, + // utils.check_output_utxo(output_utxo_store, extra_signatories)?, + // output_utxo_exchange != None, + // } + + // // bytearray.compare( + // // bytearray.drop(reference_token, 4), + // // bytearray.drop(user_token, 4), + // // ) == Equal, + // Burn -> and { + // minting.by_prefix( + // mint_flatten, + // policy_id, + // cip68.prefix_100, + // -1, + // )?, + // output_utxo_exchange != None, + // minting.by_prefix( + // mint_flatten, + // policy_id, + // cip68.prefix_222, + // -1, + // )?, + // } + // } + // } + + // // bytearray.compare( + // // bytearray.drop(reference_token, 4), + // // bytearray.drop(user_token, 4), + // // ) == Equal, + // _ -> False + // } + // _ -> False + // } + } + + else(_) { + fail + } +} diff --git a/contract/validators/store.ak b/contract/validators/store.ak new file mode 100644 index 00000000..03d0dad4 --- /dev/null +++ b/contract/validators/store.ak @@ -0,0 +1,55 @@ +use aiken/crypto.{VerificationKeyHash} +use cardano/address +use cardano/assets.{without_lovelace} +use cardano/transaction.{ + InlineDatum, Output, OutputReference, Transaction, find_input, +} +use cip68generator/types.{Remove, StoreRedeemer, Update} +use cip68generator/utils +use types/cip68.{CIP68} +use validation/find.{output_by_addr_value} + +validator store(exchange: VerificationKeyHash, exchange_fee: Int) { + spend( + datum: Option, + redeemer: StoreRedeemer, + output_reference: OutputReference, + transaction: Transaction, + ) { + True + // expect Some(datum_output) = datum + // let Transaction { inputs, outputs, extra_signatories, .. } = transaction + // let exchange_address = address.from_verification_key(exchange) + // let output_utxo_exchange = + // utils.find_output(outputs, exchange_fee, exchange_address) + // expect Some(input) = find_input(inputs, output_reference) + // let script_address = input.output.address + // let reference_token = + // input.output.value + // |> without_lovelace() + // let validator_output = + // output_by_addr_value(outputs, script_address, reference_token) + // expect InlineDatum(datum_input) = validator_output.datum + // let meradatum_output: CIP68 = datum_output + // expect metadatum_input: CIP68 = datum_input + // expect author_input: ByteArray = cip68.get(metadatum_input, "author") + // expect author_output: ByteArray = cip68.get(meradatum_output, "author") + // let check_author = author_input == author_output + + // when redeemer is { + // Update -> and { + // utils.check_output_utxo(validator_output, extra_signatories)?, + // output_utxo_exchange != None, + // check_author, + // } + // Remove -> and { + // utils.check_output_utxo(validator_output, extra_signatories)?, + // output_utxo_exchange != None, + // } + // } + } + + else(_) { + fail + } +}