Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from cardano2vn/frontend/history
Browse files Browse the repository at this point in the history
develop: onchain code with smart contract
independenceee authored Nov 4, 2024
2 parents 1352b08 + 64313ce commit 3db5e64
Showing 7 changed files with 397 additions and 72 deletions.
128 changes: 127 additions & 1 deletion contract/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,127 @@
# CIP68Generator - Contract
use aiken/collection/list
use aiken/crypto.{ScriptHash, VerificationKeyHash}
use cardano/address
use cardano/assets.{AssetName, PolicyId, without_lovelace}
use cardano/minting
use cardano/transaction.{Transaction}
use cardano/tx.{verify_signature}
use cardano/value
use cip68generator/types.{Burn, Mint, MintRedeemer}
use cip68generator/utils
use types/cip68
use validation/find

// validator - mint
// parameters (exchange_address, store_validator)
validator mint(store: ScriptHash) {
mint(redeemer: MintRedeemer, policy_id: PolicyId, transaction: Transaction) {
let Transaction { inputs, outputs, extra_signatories, mint, .. } =
transaction

let mint_flatten =
mint
|> without_lovelace()
|> assets.flatten()

when redeemer is {
Mint -> {
let first_tx_id = find.first_input_txid(inputs)
let first_tx_index = find.first_input_index(inputs)

let reference_token = utils.token_prefix(mint_flatten, cip68.prefix_100)
let user_token = utils.token_prefix(mint_flatten, cip68.prefix_222)

let check_none_token =
utils.check_none_token(user_token, reference_token)

when check_none_token is {
False -> False
True -> {
let reference_value =
assets.from_asset(policy_id, reference_token, 1)
let store_address = address.from_script(store)
let output_utxo =
find.output_by_addr_value(outputs, store_address, reference_value)

and {
first_tx_index < 256,
list.length(mint_flatten) >= 2,
minting.exact(mint_flatten, policy_id, reference_token, 1)?,
utils.check_output_utxo(output_utxo, extra_signatories)?,
}
}
}
}

Burn -> True
}

}

else(\_) {
fail
}
}

use aiken/collection/list
use aiken/crypto.{ScriptHash, VerificationKeyHash}
use cardano/address
use cardano/assets.{AssetName, PolicyId, without_lovelace}
use cardano/minting
use cardano/transaction.{Transaction}
use cardano/tx.{verify_signature}
use cardano/value
use cip68generator/types.{Burn, Mint, MintRedeemer}
use cip68generator/utils
use types/cip68
use validation/find

// validator - mint
// parameters (exchange_address, store_validator)
validator mint(store: ScriptHash) {
mint(redeemer: MintRedeemer, policy_id: PolicyId, transaction: Transaction) {
let Transaction { inputs, outputs, extra_signatories, mint, .. } =
transaction

let mint_flatten =
mint
|> without_lovelace()
|> assets.flatten()

when redeemer is {
Mint -> {
let first_tx_id = find.first_input_txid(inputs)
let first_tx_index = find.first_input_index(inputs)

let reference_token = utils.token_prefix(mint_flatten, cip68.prefix_100)
let user_token = utils.token_prefix(mint_flatten, cip68.prefix_222)

let check_none_token = utils.check_none_token(user_token, reference_token)

when reference_token is {
Some(reference_token) -> {
let reference_value =
assets.from_asset(policy_id, reference_token, 1)
let store_address = address.from_script(store)
let output_utxo =
find.output_by_addr_value(outputs, store_address, reference_value)

and {
first_tx_index < 256,
list.length(mint_flatten) >= 2,
minting.exact(mint_flatten, policy_id, reference_token, 1)?,
utils.check_output_utxo(output_utxo, extra_signatories)?,
}
}
_ -> fail @"No matching asset found for the given prefix"
}
}
Burn -> True
}

}

else(\_) {
fail
}
}
20 changes: 2 additions & 18 deletions contract/lib/cip68generator/types.ak
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
use aiken/crypto.{VerificationKeyHash}
use cardano/assets.{AssetName, PolicyId}

pub type Asset {
policy_id: PolicyId,
asset_name: AssetName,
}

pub type Extra {
address: VerificationKeyHash,
has_change: Bool,
asset: Asset,
}

pub type StoreDatum {
metadata: Pair<Data, Data>,
version: Int,
extra: Extra,
}

// the redeemer using mint validator
pub type MintRedeemer {
Mint
Burn
}

// the redeemer using store validator
pub type StoreRedeemer {
Update
Remove
Redeem
}
100 changes: 95 additions & 5 deletions contract/lib/cip68generator/utils.ak
Original file line number Diff line number Diff line change
@@ -1,15 +1,105 @@
use aiken/collection/list
use cardano/assets.{flatten, without_lovelace}
use aiken/crypto.{ScriptHash, VerificationKeyHash}
use aiken/primitive/bytearray
use cardano/address.{Address}
use cardano/assets.{AssetName, PolicyId, flatten, lovelace_of, without_lovelace}
use cardano/transaction.{InlineDatum, Output}
use cip68generator/types.{StoreDatum}
use cardano/tx
use types/cip68.{CIP68}

pub fn check_output_utxo(output: Output) -> Bool {
// check the output utxos containing the reference nft
pub fn check_output_utxo(output: Output, vks: List<VerificationKeyHash>) -> Bool {
expect InlineDatum(data) = output.datum
expect _metadatum: StoreDatum = data
expect metadatum: CIP68 = data
expect name: ByteArray = cip68.get(metadatum, "name")
expect image: ByteArray = cip68.get(metadatum, "image")
expect media_type: ByteArray = cip68.get(metadatum, "mediaType")
expect author: VerificationKeyHash = cip68.get(metadatum, "author")

let output_value =
output.value
|> without_lovelace()
|> flatten()

list.length(output_value) == 1
and {
bytearray.length(name) > 0,
bytearray.length(image) > 0,
bytearray.length(media_type) > 0,
tx.verify_signature(vks, author),
list.length(output_value) == 1,
}
}

// get asset name from mint flatten
pub fn token_prefix(
flat: List<(PolicyId, AssetName, Int)>,
prefix: ByteArray,
) -> Option<AssetName> {
let exist =
list.find(
flat,
fn((policy_id, asset_name, amount)) {
bytearray.starts_with(asset_name, prefix)
},
)
when exist is {
Some((policy_id, asset_name, amount)) -> Some(asset_name)
None -> None
}
}

pub fn check_none_token(
user_token: Option<AssetName>,
reference_token: Option<AssetName>,
) -> Bool {
if user_token == None || reference_token == None {
False
} else {
True
}
}

pub fn check_address(output: Output, address: Address) -> Bool {
output.address.payment_credential == address.payment_credential
}

// The function checks whether the output exists or not
pub fn check_none_output(
output_store: Option<Output>,
output_exchange: Option<Output>,
) -> Bool {
// If one of the 3 outputs does not exist, the function will return False
if output_store == None || output_exchange == None {
False
} else {
// Otherwise, the function will return True
True
}
}

pub fn find_output(
outputs: List<Output>,
price: Int,
address: Address,
) -> Option<Output> {
list.find(
outputs,
fn(output) { check_amount(output, price) && check_address(output, address) },
)
}

pub fn check_price_duplicate(out_sell: Output, out_royal: Output) -> Bool {
lovelace_of(out_sell.value) > lovelace_of(out_royal.value)
}

pub fn check_address_duplicate(
output_author: Output,
output_exchange: Output,
) -> Bool {
output_author.address.payment_credential == output_exchange.address.payment_credential
}

// The function checks the amount
pub fn check_amount(output: Output, price: Int) -> Bool {
lovelace_of(output.value) >= price
}
58 changes: 42 additions & 16 deletions contract/src/txbuilder/cip68.txbuilder.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import {
AssetMetadata,
metadataToCip68,
mConStr1,
deserializeAddress
} from "@meshsdk/core";
import { ICip68Contract } from "../interface/icip68.interface";
import { MeshAdapter } from "../adapters/mesh.adapter";
@@ -28,25 +29,28 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {
protected mintCompileCode: string = this.readValidator(plutus, title.mint);
protected storeCompileCode: string = this.readValidator(plutus, title.store);

protected mintScriptCbor = applyParamsToScript(this.mintCompileCode, []);
protected storeScriptCbor = applyParamsToScript(this.storeCompileCode, []);
protected storeScriptCbor = applyParamsToScript(this.storeCompileCode, ["9dcd4b00b1d25d24c07a82c02af5e955e42271a2548136df4af35b38", BigInt(1)]);

protected mintScript: PlutusScript = {
code: this.mintScriptCbor,
version: "V3",
};
protected storeScript: PlutusScript = {
code: this.storeScriptCbor,
version: "V3",
};

protected policyId = resolveScriptHash(this.mintScriptCbor, "V3");
protected storeAddress = serializePlutusScript(
protected storeAddress = serializePlutusScript(
this.storeScript,
undefined,
APP_NETWORK,
false,
).address;
protected storeScriptHash = deserializeAddress(this.storeAddress).scriptHash;
protected mintScriptCbor = applyParamsToScript(this.mintCompileCode, [
"9dcd4b00b1d25d24c07a82c02af5e955e42271a2548136df4af35b38", BigInt(1),this.storeScriptHash
]);
protected mintScript: PlutusScript = {
code: this.mintScriptCbor,
version: "V3",
};
protected policyId = resolveScriptHash(this.mintScriptCbor, "V3");


/**
*
@@ -63,19 +67,24 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {
quantity: string;
}) => {
const { utxos, walletAddress, collateral } = await this.getWalletForTx();

const utxoRef: UTxO = await this.getUtxoForTx(
MINT_REFERENCE_SCRIPT_ADDRESS,
MINT_REFERENCE_SCRIPT_HASH,
);

console.log(deserializeAddress(walletAddress).pubKeyHash)
const unsignedTx = this.meshTxBuilder
.mintPlutusScriptV3()
.mint(quantity, this.policyId, CIP68_222(stringToHex(assetName)))
.mintTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex)
// .mintTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex)
.mintingScript(this.mintScriptCbor)
.mintRedeemerValue(mConStr0([]))

.mintPlutusScriptV3()
.mint("1", this.policyId, CIP68_100(stringToHex(assetName)))
.mintTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex)
.mintingScript(this.mintScriptCbor)
// .mintTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex)
.mintRedeemerValue(mConStr0([]))

.txOut(this.storeAddress, [
@@ -85,7 +94,15 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {
},
])
.txOutInlineDatumValue(metadataToCip68(metadata))
.txOut("addr_test1qzwu6jcqk8f96fxq02pvq2h4a927ggn35f2gzdklfte4kwx0sd5zdvsat2chsyyjxkjxcg6uz2y46avd46mzqdgdy3dsckqxs4", [
{
unit: "lovelace",
quantity: "1500000",
},
])

.changeAddress(walletAddress)
.requiredSignerHash(deserializeAddress(walletAddress).pubKeyHash)
.selectUtxosFrom(utxos)
.txInCollateral(
collateral.input.txHash,
@@ -124,10 +141,11 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {

.mintPlutusScriptV3()
.mint(quantity, this.policyId, CIP68_222(stringToHex(assetName)))
.mintTxInReference(
mintUtxoRef.input.txHash,
mintUtxoRef.input.outputIndex,
)
.mintingScript(this.mintScriptCbor)
// .mintTxInReference(
// mintUtxoRef.input.txHash,
// mintUtxoRef.input.outputIndex,
// )
.mintRedeemerValue(mConStr1([]))

.spendingPlutusScriptV3()
@@ -154,7 +172,7 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {
mintUtxoRef.input.outputIndex,
)
.mintRedeemerValue(mConStr1([]))

.requiredSignerHash(deserializeAddress(walletAddress).pubKeyHash)
.changeAddress(walletAddress)
.selectUtxosFrom(utxos)
.txInCollateral(
@@ -207,6 +225,14 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract {
},
])
.txOutInlineDatumValue(metadataToCip68(metadata))

.txOut("addr_test1qzwu6jcqk8f96fxq02pvq2h4a927ggn35f2gzdklfte4kwx0sd5zdvsat2chsyyjxkjxcg6uz2y46avd46mzqdgdy3dsckqxs4", [
{
unit: "lovelace",
quantity: "1000000",
},
])
.requiredSignerHash(deserializeAddress(walletAddress).pubKeyHash)
.changeAddress(walletAddress)
.selectUtxosFrom(utxos)
.txInCollateral(
38 changes: 20 additions & 18 deletions contract/tests/cip68.test.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { describe, test, expect, beforeEach } from "@jest/globals";
import {
BlockfrostProvider,
BrowserWallet,
deserializeAddress,
KoiosProvider,
MeshTxBuilder,
MeshWallet,
@@ -45,12 +46,13 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () {
});

const unsignedTx: string = await cip68Contract.mint({
assetName: "CIP68 Generators",
assetName: "CIP68 Generators000001",
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",
});
@@ -60,24 +62,24 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () {
expect(txHash.length).toBe(64);
});

test("Burn", async function () {
const cip68Contract: Cip68Contract = new Cip68Contract({
fetcher: blockfrostProvider,
wallet: wallet,
meshTxBuilder: meshTxBuilder,
});
// test("Burn", async function () {
// const cip68Contract: Cip68Contract = new Cip68Contract({
// fetcher: blockfrostProvider,
// wallet: wallet,
// meshTxBuilder: meshTxBuilder,
// });

const unsignedTx: string = await cip68Contract.burn({
assetName: "CIP68 Generators",
txHash:
"728fa7f14b3652a34dfd0f920b5739ede6bda1a88e9ffc52d74636bb41007235",
quantity: "-1",
});
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.burn({
// assetName: "CIP68 Generators",
// txHash:
// "728fa7f14b3652a34dfd0f920b5739ede6bda1a88e9ffc52d74636bb41007235",
// quantity: "-1",
// });
// const signedTx = await wallet.signTx(unsignedTx, true);
// const txHash = await wallet.submitTx(signedTx);
// console.log(txHash);
// expect(txHash.length).toBe(64);
// });

// test("Update", async function () {
// const cip68Contract: Cip68Contract = new Cip68Contract({
78 changes: 72 additions & 6 deletions contract/validators/mint.ak
Original file line number Diff line number Diff line change
@@ -1,18 +1,84 @@
use aiken/crypto.{VerificationKeyHash}
use cardano/assets.{PolicyId}
use aiken/collection/list
use aiken/crypto.{ScriptHash, VerificationKeyHash}
use cardano/address
use cardano/assets.{AssetName, PolicyId, without_lovelace}
use cardano/minting
use cardano/transaction.{Transaction}
use cardano/tx.{verify_signature}
use cardano/value
use cip68generator/types.{Burn, Mint, MintRedeemer}
use cip68generator/utils
use types/cip68
use validation/find

// validator - mint
// parameters (exchange_address, store_validator)
validator mint(exchange: VerificationKeyHash, store: VerificationKeyHash) {
mint(redeemer: MintRedeemer, _policy_id: PolicyId, transaction: Transaction) {
let Transaction { inputs, outputs, extra_signatories, .. } = transaction
validator mint(
exchange: VerificationKeyHash,
exchange_fee: Int,
store: ScriptHash,
) {
mint(redeemer: MintRedeemer, policy_id: PolicyId, transaction: Transaction) {
let Transaction { inputs, outputs, extra_signatories, mint, .. } =
transaction

let mint_flatten =
mint
|> without_lovelace()
|> assets.flatten()
let exchange_address = address.from_verification_key(exchange)
let output_utxo_exchange =
utils.find_output(outputs, exchange_fee, exchange_address)

when redeemer is {
Mint -> {

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 check_none_token =
utils.check_none_token(user_token_option, reference_token_option)

when check_none_token is {
False -> False
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,
)
// let output_utxo_user = find.output_by_addr_value(outputs)
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,
}
}
_ -> False
}
}
}

Burn -> True
}
}
47 changes: 39 additions & 8 deletions contract/validators/store.ak
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
use cardano/transaction.{OutputReference, Transaction}
use cip68generator/types.{Redeem, Remove, StoreRedeemer, Update}
use aiken/crypto.{ScriptHash, VerificationKeyHash}
use cardano/address
use cardano/assets.{flatten, 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 {
validator store(exchange: VerificationKeyHash, exchange_fee: Int) {
spend(
_datum: Option<CIP68>,
datum: Option<CIP68>,
redeemer: StoreRedeemer,
_output_reference: OutputReference,
_transaction: Transaction,
output_reference: OutputReference,
transaction: Transaction,
) {
expect Some(datum_output) = datum
let Transaction { inputs, outputs, extra_signatories, .. } = transaction
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)
let exchange_address = address.from_verification_key(exchange)
let output_utxo_exchange =
utils.find_output(outputs, exchange_fee, exchange_address)
when redeemer is {
Update -> True
Update -> {
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
and {
utils.check_output_utxo(validator_output, extra_signatories)?,
output_utxo_exchange != None,
check_author,
}
}
Remove -> True
Redeem -> True
}
}

0 comments on commit 3db5e64

Please sign in to comment.