Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #3

Merged
merged 2 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
# Aiken compilation artifacts
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
.vscode
# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

#lock files
package-lock.json
bun.lockb
# local env files
.env*.local
.env
# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


artifacts/
# Aiken's project working directory
build/
# Aiken's default documentation export
docs/

yarn.lock
stack.env
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.github/
.husky
19 changes: 19 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false
}
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/

const jestConfig = {
testTimeout: 60000,
testEnvironment: "node",
transform: {
"^.+.tsx?$": ["ts-jest", {}],
},
};

export default jestConfig;
1 change: 1 addition & 0 deletions lib/marketplace/types.ak
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub type MarketplaceDatum {
seller: VerificationKeyHash,
price: Int,
royalties: Int,
author: VerificationKeyHash,
order: Option<Ord>,
}

Expand Down
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "cardano-marketplace",
"version": "1.0.0",
"description": "🇻🇳 Cardano App Development Course: A Step-by-Step Guide for Beginners - From basic Web development to use Cardano Libraries and interacting with Smart Contracts",
"main": "index.ts",
"directories": {
"lib": "lib",
"test": "tests"
},
"devDependencies": {},
"scripts": {
"test": "jest --runInBand",
"check:contract": "aiken check",
"build:contract": "aiken build"
},
"author": "",
"license": "ISC",
"dependencies": {
"@jest/globals": "^29.7.0",
"@meshsdk/core": "^1.8.4",
"cbor": "^10.0.3"
}
}
8 changes: 6 additions & 2 deletions plutus.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"validators": [
{
"title": "contract.contract.spend",
"title": "marketplace.contract.spend",
"datum": {
"title": "_datum_option",
"schema": {
Expand Down Expand Up @@ -43,7 +43,7 @@
"hash": "0fe2b5c85ca3b5609d106fff471e854882c8fcc681d88dc928c241a8"
},
{
"title": "contract.contract.else",
"title": "marketplace.contract.else",
"parameters": [
{
"title": "_platform",
Expand Down Expand Up @@ -129,6 +129,10 @@
"title": "royalties",
"$ref": "#/definitions/Int"
},
{
"title": "author",
"$ref": "#/definitions/VerificationKeyHash"
},
{
"title": "order",
"$ref": "#/definitions/Option$marketplace~1types~1Ord"
Expand Down
120 changes: 120 additions & 0 deletions scripts/adapters/mesh.adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
applyParamsToScript,
deserializeAddress,
IFetcher,
MeshTxBuilder,
MeshWallet,
PlutusScript,
serializePlutusScript,
UTxO,
} from "@meshsdk/core";
import { Plutus } from "../types";
import { APP_WALLET_ADDRESS, title } from "../constants";
import plutus from "../../plutus.json";
import { appNetworkId } from "../constants";
import { blockfrostProvider } from "../libs/blockfrost";
import convertInlineDatum from "../helpers/convert-inline-datum";

export class MeshAdapter {
protected fetcher: IFetcher;
protected wallet: MeshWallet;
protected pubKeyExchange: string;
protected meshTxBuilder: MeshTxBuilder;
protected marketplaceAddress: string;
protected marketplaceScript: PlutusScript;
protected marketplaceScriptCbor: string;
protected marketplaceCompileCode: string;
constructor({ wallet = null! }: { wallet?: MeshWallet }) {
this.wallet = wallet;
this.fetcher = blockfrostProvider;
this.meshTxBuilder = new MeshTxBuilder({
fetcher: this.fetcher,
evaluator: blockfrostProvider,
});
this.pubKeyExchange = deserializeAddress(APP_WALLET_ADDRESS).pubKeyHash;
this.marketplaceCompileCode = this.readValidator(plutus as Plutus, title.marketplace);

this.marketplaceScriptCbor = applyParamsToScript(this.marketplaceCompileCode, [
this.pubKeyExchange,
BigInt(1),
]);

this.marketplaceScript = {
code: this.marketplaceScriptCbor,
version: "V3",
};

this.marketplaceAddress = serializePlutusScript(
this.marketplaceScript,
undefined,
appNetworkId,
false,
).address;
}

protected getWalletForTx = async (): Promise<{
utxos: UTxO[];
collateral: UTxO;
walletAddress: string;
}> => {
const utxos = await this.wallet.getUtxos();
const collaterals = await this.wallet.getCollateral();
const walletAddress = this.wallet.getChangeAddress();
if (!utxos || utxos.length === 0)
throw new Error("No UTXOs found in getWalletForTx method.");

if (!collaterals || collaterals.length === 0)
throw new Error("No collateral found in getWalletForTx method.");

if (!walletAddress) throw new Error("No wallet address found in getWalletForTx method.");

return { utxos, collateral: collaterals[0], walletAddress };
};

protected getUtxoForTx = async (address: string, txHash: string) => {
const utxos: UTxO[] = await this.fetcher.fetchAddressUTxOs(address);
const utxo = utxos.find(function (utxo: UTxO) {
return utxo.input.txHash === txHash;
});

if (!utxo) throw new Error("No UTXOs found in getUtxoForTx method.");
return utxo;
};

protected readValidator = function (plutus: Plutus, title: string): string {
const validator = plutus.validators.find(function (validator) {
return validator.title === title;
});

if (!validator) {
throw new Error(`${title} validator not found.`);
}

return validator.compiledCode;
};

protected readPlutusData = async ({ plutusData }: { plutusData: string }) => {
const datum = await convertInlineDatum({ inlineDatum: plutusData });
return {
policyId: datum?.fields[0].bytes,
assetName: datum?.fields[1].bytes,
seller: datum?.fields[2].bytes,
price: datum?.fields[3].int,
royalties: datum?.fields[4].int,
author: datum?.fields[5].bytes,
order: {
owner: datum?.fields[6].fields[0].bytes,
price: datum?.fields[6].fields[1].int,
},
};
};

protected getAddressUTXOAsset = async (address: string, unit: string) => {
const utxos = await this.fetcher.fetchAddressUTxOs(address, unit);
return utxos[utxos.length - 1];
};

protected getAddressUTXOAssets = async (address: string, unit: string) => {
return await this.fetcher.fetchAddressUTxOs(address, unit);
};
}
17 changes: 17 additions & 0 deletions scripts/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Network } from "@meshsdk/core";

export const APP_WALLET_ADDRESS =
process.env.APP_WALLET_ADDRESS ||
"addr_test1qzjzr7f3yj3k4jky7schc55qjclaw6fhc3zfnrarma9l3579hwurrx9w7uhz99zdc3fmmzwel6hac404zyywjl5jhnls09rtm6";
export const EXCHANGE_FEE_PRICE = process.env.EXCHANGE_FEE_PRICE || "1000000"; //lovelace

const BLOCKFROST_API_KEY = process.env.BLOCKFROST_API_KEY || "";
const title = {
marketplace: "marketplace.contract.spend",
};
const appNetwork: Network =
(process.env.NEXT_PUBLIC_APP_NETWORK?.toLowerCase() as Network) || "preprod";

const appNetworkId = appNetwork === "mainnet" ? 1 : 0;

export { appNetwork, appNetworkId, BLOCKFROST_API_KEY, title };
47 changes: 47 additions & 0 deletions scripts/helpers/convert-inline-datum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { decodeFirst, Tagged } from "cbor";

type Props = {
inlineDatum: string;
};

function convertToJSON(decoded: any) {
if (Buffer.isBuffer(decoded)) {
return { bytes: decoded.toString("hex") };
} else if (typeof decoded === "number") {
return { int: decoded };
} else if (typeof decoded === "bigint") {
return { int: decoded.toString() };
} else if (decoded instanceof Tagged) {
const fields = decoded.value.map(function (item: any) {
if (Buffer.isBuffer(item)) {
return { bytes: item.toString("hex") };
} else if (typeof item === "number") {
return { int: item };
} else {
return null;
}
});

return {
fields: fields.filter(function (item: any) {
return item !== null;
}),
constructor: decoded.tag,
};
}
}
const convertInlineDatum = async function ({ inlineDatum }: Props) {
try {
const cborDatum: Buffer = Buffer.from(inlineDatum, "hex");
const decoded = await decodeFirst(cborDatum);
const jsonStructure = {
fields: decoded.value.map((item: any) => convertToJSON(item)),
constructor: decoded.tag,
};
return jsonStructure;
} catch (error) {
return null;
}
};

export default convertInlineDatum;
1 change: 1 addition & 0 deletions scripts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./txbuilders/marketplace.txbuilder";
1 change: 1 addition & 0 deletions scripts/interfaces/imarketplace.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface IMarketplaceContract {}
18 changes: 18 additions & 0 deletions scripts/libs/blockfrost/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { BlockfrostProvider } from "@meshsdk/core";
import { BLOCKFROST_API_KEY } from "../../constants";

const blockfrostProviderSingleton = () => {
return new BlockfrostProvider(BLOCKFROST_API_KEY);
};

declare const globalThis: {
blockfrostProviderGlobal: ReturnType<typeof blockfrostProviderSingleton>;
} & typeof global;

const blockfrostProvider = globalThis.blockfrostProviderGlobal ?? blockfrostProviderSingleton();

if (process.env.NODE_ENV !== "production") {
globalThis.blockfrostProviderGlobal = blockfrostProvider;
}

export { blockfrostProvider };
Loading
Loading