diff --git a/contract/README.md b/contract/README.md index 593d3ea2..66cc1991 100644 --- a/contract/README.md +++ b/contract/README.md @@ -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 +} +} diff --git a/contract/lib/cip68generator/types.ak b/contract/lib/cip68generator/types.ak index 63f176de..6fcf1fc8 100644 --- a/contract/lib/cip68generator/types.ak +++ b/contract/lib/cip68generator/types.ak @@ -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, - 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 } diff --git a/contract/lib/cip68generator/utils.ak b/contract/lib/cip68generator/utils.ak index 0b7502c1..4b565b26 100644 --- a/contract/lib/cip68generator/utils.ak +++ b/contract/lib/cip68generator/utils.ak @@ -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) -> 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 { + 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, + reference_token: Option, +) -> 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_exchange: Option, +) -> 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, + price: Int, + address: Address, +) -> Option { + 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 } diff --git a/contract/src/txbuilder/cip68.txbuilder.ts b/contract/src/txbuilder/cip68.txbuilder.ts index 1b6b4290..879e0abb 100644 --- a/contract/src/txbuilder/cip68.txbuilder.ts +++ b/contract/src/txbuilder/cip68.txbuilder.ts @@ -10,6 +10,8 @@ import { UTxO, AssetMetadata, metadataToCip68, + mConStr1, + deserializeAddress } from "@meshsdk/core"; import { ICip68Contract } from "../interface/icip68.interface"; import { MeshAdapter } from "../adapters/mesh.adapter"; @@ -27,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"); + /** * @@ -62,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, ); - const unsignedTx = await this.meshTxBuilder + + console.log(deserializeAddress(walletAddress).pubKeyHash) + const unsignedTx = this.meshTxBuilder .mintPlutusScriptV3() - .mint(String(quantity), this.policyId, CIP68_222(stringToHex(assetName))) - .mintTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex) + .mint(quantity, this.policyId, CIP68_222(stringToHex(assetName))) + // .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, [ @@ -84,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, @@ -95,6 +113,78 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract { return unsignedTx.complete(); }; + burn = async ({ + assetName, + quantity, + txHash, + }: { + assetName: string; + quantity: string; + txHash: string; + }) => { + const { utxos, walletAddress, collateral } = await this.getWalletForTx(); + + const mintUtxoRef: UTxO = await this.getUtxoForTx( + MINT_REFERENCE_SCRIPT_ADDRESS, + MINT_REFERENCE_SCRIPT_HASH, + ); + const storeUtxoRef: UTxO = await this.getUtxoForTx( + STORE_REFERENCE_SCRIPT_ADDRESS, + STORE_REFERENCE_SCRIPT_HASH, + ); + + const storeUtxo = await this.getUtxoForTx(this.storeAddress, txHash); + const userUtxo = await this.getUtxoForTx(walletAddress, txHash); + + const unsignedTx = this.meshTxBuilder + // .txIn(storeUtxo.input.txHash, storeUtxo.input.outputIndex) + + .mintPlutusScriptV3() + .mint(quantity, this.policyId, CIP68_222(stringToHex(assetName))) + .mintingScript(this.mintScriptCbor) + // .mintTxInReference( + // mintUtxoRef.input.txHash, + // mintUtxoRef.input.outputIndex, + // ) + .mintRedeemerValue(mConStr1([])) + + .spendingPlutusScriptV3() + .txIn(storeUtxo.input.txHash, storeUtxo.input.outputIndex) + .txInInlineDatumPresent() + .txInRedeemerValue(mConStr1([])) + .spendingTxInReference( + storeUtxoRef.input.txHash, + storeUtxoRef.input.outputIndex, + ) + + .txOut(walletAddress, [ + { + unit: this.policyId + CIP68_100(stringToHex(assetName)), + quantity: "1", + }, + ]) + .txOutInlineDatumValue("") + + .mintPlutusScriptV3() + .mint(quantity, this.policyId, CIP68_100(stringToHex(assetName))) + .mintTxInReference( + mintUtxoRef.input.txHash, + mintUtxoRef.input.outputIndex, + ) + .mintRedeemerValue(mConStr1([])) + .requiredSignerHash(deserializeAddress(walletAddress).pubKeyHash) + .changeAddress(walletAddress) + .selectUtxosFrom(utxos) + .txInCollateral( + collateral.input.txHash, + collateral.input.outputIndex, + collateral.output.amount, + collateral.output.address, + ); + + return unsignedTx.complete(); + }; + update = async ({ assetName, metadata, @@ -127,6 +217,7 @@ export class Cip68Contract extends MeshAdapter implements ICip68Contract { .txInInlineDatumPresent() .txInRedeemerValue(mConStr0([])) .spendingTxInReference(utxoRef.input.txHash, utxoRef.input.outputIndex) + .txOut(this.storeAddress, [ { unit: this.policyId + CIP68_100(stringToHex(assetName)), @@ -134,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( diff --git a/contract/tests/cip68.test.ts b/contract/tests/cip68.test.ts index 08722672..b4d6e849 100644 --- a/contract/tests/cip68.test.ts +++ b/contract/tests/cip68.test.ts @@ -3,6 +3,7 @@ import { describe, test, expect, beforeEach } from "@jest/globals"; import { BlockfrostProvider, BrowserWallet, + deserializeAddress, KoiosProvider, MeshTxBuilder, MeshWallet, @@ -37,22 +38,42 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () { }); }); - // test("Mint", async function () { + test("Mint", async function () { + const cip68Contract: Cip68Contract = new Cip68Contract({ + fetcher: blockfrostProvider, + wallet: wallet, + meshTxBuilder: meshTxBuilder, + }); + + const unsignedTx: string = await cip68Contract.mint({ + 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", + }); + const signedTx = await wallet.signTx(unsignedTx, true); + const txHash = await wallet.submitTx(signedTx); + console.log(txHash); + expect(txHash.length).toBe(64); + }); + + // test("Burn", async function () { // const cip68Contract: Cip68Contract = new Cip68Contract({ // fetcher: blockfrostProvider, // wallet: wallet, // meshTxBuilder: meshTxBuilder, // }); - // const unsignedTx: string = await cip68Contract.mint({ + // const unsignedTx: string = await cip68Contract.burn({ // assetName: "CIP68 Generators", - // metadata: { - // name: "CIP68 Generators", - // image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua", - // mediaType: "image/jpg", - // description: "Open source dynamic assets (Token/NFT) generator (CIP68)", - // }, - // quantity: "1", + // txHash: + // "728fa7f14b3652a34dfd0f920b5739ede6bda1a88e9ffc52d74636bb41007235", + // quantity: "-1", // }); // const signedTx = await wallet.signTx(unsignedTx, true); // const txHash = await wallet.submitTx(signedTx); @@ -74,7 +95,8 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () { // image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua", // mediaType: "image/jpg", // }, - // txHash: "d3c92245ad396c3c05e2530be2e0d3a3adbf1871113fcb18a197c23e4fdfcd1a", + // txHash: + // "d3c92245ad396c3c05e2530be2e0d3a3adbf1871113fcb18a197c23e4fdfcd1a", // }); // const signedTx = await wallet.signTx(unsignedTx, true); // const txHash = await wallet.submitTx(signedTx); @@ -82,18 +104,18 @@ describe("Mint, Burn, Update, Remove Assets (NFT/TOKEN) CIP68", function () { // expect(txHash.length).toBe(64); // }); - 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); - }); + // 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); + // }); // test('Store Reference Script', async function () { // const cip68Contract: Cip68Contract = new Cip68Contract({ diff --git a/contract/validators/mint.ak b/contract/validators/mint.ak index 08d55cd0..6583268d 100644 --- a/contract/validators/mint.ak +++ b/contract/validators/mint.ak @@ -1,14 +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, + 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) -validator mint(issuer: VerificationKeyHash) { - mint(redeemer: MintRedeemer, _policy_id: PolicyId, transaction: Transaction) { - let Transaction { extra_signatories, .. } = transaction when redeemer is { - Mint -> verify_signature(extra_signatories, issuer) + 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 } } diff --git a/contract/validators/store.ak b/contract/validators/store.ak index c12b73bb..d4f40757 100644 --- a/contract/validators/store.ak +++ b/contract/validators/store.ak @@ -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, + datum: Option, 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 } } diff --git a/eslint.config.mjs b/eslint.config.mjs index 78b07dd0..af5840f7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,55 +15,55 @@ const compat = new FlatCompat({ }); const eslintConfig = [ - // { - // ignores: ["**/node_modules", "**/.eslintrc.js", "src/components/ui/**"], - // }, - // ...compat.extends( - // "eslint:recommended", - // "plugin:@typescript-eslint/eslint-recommended", - // "plugin:@typescript-eslint/recommended", - // "plugin:jsx-a11y/recommended", - // "plugin:prettier/recommended", - // "next", - // "next/core-web-vitals", - // ), - // { - // plugins: { - // "@typescript-eslint": typescriptEslint, - // }, - // languageOptions: { - // globals: { - // ...globals.browser, - // ...globals.amd, - // ...globals.node, - // }, - // parser: tsParser, - // ecmaVersion: 5, - // sourceType: "commonjs", - // parserOptions: { - // project: true, - // tsconfigRootDir: __dirname, - // }, - // }, - // rules: { - // "prettier/prettier": "error", - // "react/react-in-jsx-scope": "off", - // "jsx-a11y/anchor-is-valid": [ - // "error", - // { - // components: ["Link"], - // specialLink: ["hrefLeft", "hrefRight"], - // aspects: ["invalidHref", "preferButton"], - // }, - // ], - // "react/prop-types": 0, - // "@typescript-eslint/no-unused-vars": 1, - // "react/no-unescaped-entities": 0, - // "@typescript-eslint/explicit-module-boundary-types": "off", - // "@typescript-eslint/no-var-requires": "off", - // "@typescript-eslint/ban-ts-comment": "off", - // }, - // }, + { + ignores: ["**/node_modules", "**/.eslintrc.js", "src/components/ui/**"], + }, + ...compat.extends( + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jsx-a11y/recommended", + "plugin:prettier/recommended", + "next", + "next/core-web-vitals", + ), + { + plugins: { + "@typescript-eslint": typescriptEslint, + }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.amd, + ...globals.node, + }, + parser: tsParser, + ecmaVersion: 5, + sourceType: "commonjs", + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, + }, + rules: { + "prettier/prettier": "error", + "react/react-in-jsx-scope": "off", + "jsx-a11y/anchor-is-valid": [ + "error", + { + components: ["Link"], + specialLink: ["hrefLeft", "hrefRight"], + aspects: ["invalidHref", "preferButton"], + }, + ], + "react/prop-types": 0, + "@typescript-eslint/no-unused-vars": 1, + "react/no-unescaped-entities": 0, + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/ban-ts-comment": "off", + }, + }, ]; export default eslintConfig; diff --git a/next.config.mjs b/next.config.mjs index 908c860c..25b527c1 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -16,7 +16,7 @@ const nextConfig = { { protocol: "https", hostname: "ipfs.io", - pathname: "/ipfs/**", + pathname: "/**", }, ], }, diff --git a/package.json b/package.json index 2a3c3299..c08370e5 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "lint": "next lint --fix", "format": "prettier --write './**/*.{js,jsx,ts,tsx,css,json}' --config ./.prettierrc", "prepare": "husky", - "test": "jest" + "test": "jest", + "build:contract": "cd ./contract && aiken build" }, "dependencies": { - "@meshsdk/core": "^1.7.9", - "@meshsdk/react": "^1.7.9", + "@hookform/resolvers": "^3.9.1", + "@meshsdk/core": "^1.7.11", + "@meshsdk/react": "^1.7.11", "@prisma/client": "^5.21.1", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -25,7 +27,9 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -36,17 +40,19 @@ "clsx": "^2.1.1", "crypto-js": "^4.2.0", "date-fns": "^4.1.0", - "framer-motion": "^11.11.10", + "framer-motion": "^11.11.11", "lodash": "^4.17.21", "lucide-react": "^0.447.0", - "next": "^15.0.1", + "next": "^15.0.2", "next-auth": "^5.0.0-beta.25", "node-cache": "^5.1.2", + "prism-react-renderer": "^2.4.0", "react": "^19.0.0-rc-fb9a90fa48-20240614", "react-copy-to-clipboard": "^5.1.0", "react-countup": "^6.5.3", "react-day-picker": "8.10.1", "react-dom": "^19.0.0-rc-fb9a90fa48-20240614", + "react-hook-form": "^7.53.1", "react-icons": "^5.3.0", "react-simple-typewriter": "^5.0.1", "react-vertical-timeline-component": "^3.6.0", @@ -55,10 +61,11 @@ "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "yarn": "^1.22.22", - "zustand": "^5.0.0" + "zod": "^3.23.8", + "zustand": "^5.0.1" }, "devDependencies": { - "@eslint/compat": "^1.2.1", + "@eslint/compat": "^1.2.2", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.13.0", "@jest/globals": "^29.7.0", @@ -66,14 +73,15 @@ "@types/crypto-js": "^4.2.2", "@types/dotenv": "^6.1.1", "@types/jest": "^29.5.14", - "@types/lodash": "^4.17.12", - "@types/node": "^20.17.0", + "@types/lodash": "^4.17.13", + "@types/node": "^20.17.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@types/react-vertical-timeline-component": "^3.3.6", "dotenv": "^16.4.5", "eslint": "^9.13.0", "eslint-config-next": "15.0.1", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "globals": "^15.11.0", "husky": "^9.1.6", diff --git a/src/app/(app)/dashboard/(profile)/_components/profile-page.tsx b/src/app/(app)/dashboard/(profile)/_components/profile-page.tsx index d0a535d2..192ecdf2 100644 --- a/src/app/(app)/dashboard/(profile)/_components/profile-page.tsx +++ b/src/app/(app)/dashboard/(profile)/_components/profile-page.tsx @@ -4,20 +4,18 @@ import { Button } from "@/components/ui/button"; import { appImage, walletImage } from "@/public/images"; import Image from "next/image"; import Link from "next/link"; -import { Pagination } from "swiper/modules"; import { IoLocation } from "react-icons/io5"; import { Swiper, SwiperSlide } from "swiper/react"; -import { VscVerified } from "react-icons/vsc"; import Asset from "./asset"; export default function ProfilePage() { return (
-