diff --git a/examples/hello_world/.gitignore b/examples/hello_world/.gitignore index a0f888da..279c2b8f 100644 --- a/examples/hello_world/.gitignore +++ b/examples/hello_world/.gitignore @@ -2,3 +2,5 @@ artifacts/ build/ *.sk *.addr +node_modules +.DS_Store \ No newline at end of file diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md index 8f31e00e..ab770e9d 100644 --- a/examples/hello_world/README.md +++ b/examples/hello_world/README.md @@ -1,33 +1,54 @@ # Hello, World! -An example of an Hello, World! contract using Aiken and [Lucid](https://github.com/spacebudz/lucid). +An example of an Hello, World! contract using Aiken and [Mesh](https://meshjs.dev/). -See the [full tutorial on aiken-lang.org](https://aiken-lang.org/getting-started/hello-world). +See the [full tutorial on aiken-lang.org](https://aiken-lang.org/example--hello-world/end-to-end/mesh). -## Building +## Setup + +### Building ``` aiken build ``` -## Generating Credentials +### Initializing workspace ``` -deno run --allow-net --allow-write generate-credentials.ts +npm init -y +npm install @meshsdk/core tsx ``` -## Locking Funds +### Setup environment variables + +``` +export BLOCKFROST_PROJECT_ID=preprod... +``` + +## Usage + +### Generating Credentials + +``` +npx tsx generate-credentials.ts +``` + +### Locking Funds > **Warning** Require `BLOCKFROST_API_KEY` environment variable to be set. ``` -deno run --allow-net --allow-read --allow-env hello_world-lock.ts +npx tsx lock.ts ``` -## Unlocking Funds +Successful transaction hash: `bfa4818940831dff961a2f097e1aef9bf626de744fd96abfd2be7d6b61afb270` (preprod) + +### Unlocking Funds > **Warning** Require `BLOCKFROST_API_KEY` environment variable to be set. ``` -deno run --allow-net --allow-read --allow-env hello_world-unlock.ts TRANSACTION_ID_FROM_LOCK +npx tsx unlock.ts TRANSACTION_ID_FROM_LOCK ``` + +Successful transaction hash: `1f8f3abac70c3a71c6aa943b4b9a6ac002e63a69225eb59305c3cd663cda3dd7` (preprod) diff --git a/examples/hello_world/common.ts b/examples/hello_world/common.ts new file mode 100644 index 00000000..70a61c85 --- /dev/null +++ b/examples/hello_world/common.ts @@ -0,0 +1,54 @@ +import fs from "node:fs"; +import { + BlockfrostProvider, + MeshTxBuilder, + MeshWallet, + serializePlutusScript, + UTxO, +} from "@meshsdk/core"; +import { applyParamsToScript } from "@meshsdk/core-csl"; +import blueprint from "./plutus.json"; + +const blockchainProvider = new BlockfrostProvider(process.env.BLOCKFROST_PROJECT_ID!); + +// wallet for signing transactions +export const wallet = new MeshWallet({ + networkId: 0, + fetcher: blockchainProvider, + submitter: blockchainProvider, + key: { + type: "root", + bech32: fs.readFileSync("me.sk").toString(), + }, +}); + +export function getScript() { + const scriptCbor = applyParamsToScript( + blueprint.validators[0].compiledCode, + [] + ); + + const scriptAddr = serializePlutusScript( + { code: scriptCbor, version: "V3" }, + ).address; + + return { scriptCbor, scriptAddr }; +} + +// reusable function to get a transaction builder +export function getTxBuilder() { + return new MeshTxBuilder({ + fetcher: blockchainProvider, + submitter: blockchainProvider, + }); +} + +// reusable function to get a UTxO by transaction hash +export async function getUtxoByTxHash(txHash: string): Promise { + const utxos = await blockchainProvider.fetchUTxOs(txHash); + if (utxos.length === 0) { + throw new Error("UTxO not found"); + } + return utxos[0]; +} + diff --git a/examples/hello_world/generate-credentials.ts b/examples/hello_world/generate-credentials.ts index dde0352d..1836b318 100644 --- a/examples/hello_world/generate-credentials.ts +++ b/examples/hello_world/generate-credentials.ts @@ -1,14 +1,16 @@ -import { Lucid } from "https://deno.land/x/lucid@0.8.3/mod.ts"; - -const lucid = await Lucid.new(undefined, "Preview"); - -const privateKey = lucid - .utils - .generatePrivateKey(); -await Deno.writeTextFile("key.sk", privateKey); - -const address = await lucid - .selectWalletFromPrivateKey(privateKey) - .wallet - .address(); -await Deno.writeTextFile("key.addr", address); +import { MeshWallet } from '@meshsdk/core'; +import fs from 'node:fs'; + +const secret_key = MeshWallet.brew(true) as string; + +fs.writeFileSync('me.sk', secret_key); + +const wallet = new MeshWallet({ + networkId: 0, + key: { + type: 'root', + bech32: secret_key, + }, +}); + +fs.writeFileSync('me.addr', wallet.getUnusedAddresses()[0]); \ No newline at end of file diff --git a/examples/hello_world/hello_world-lock.ts b/examples/hello_world/hello_world-lock.ts deleted file mode 100644 index 8acdfd3a..00000000 --- a/examples/hello_world/hello_world-lock.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Blockfrost, - Constr, - Data, - fromHex, - Lucid, - SpendingValidator, - toHex, - TxHash, -} from "https://deno.land/x/lucid@0.8.3/mod.ts"; -import * as cbor from "https://deno.land/x/cbor@v1.4.1/index.js"; - -const lucid = await Lucid.new( - new Blockfrost( - "https://cardano-preview.blockfrost.io/api/v0", - Deno.env.get("BLOCKFROST_API_KEY"), - ), - "Preview", -); - -lucid.selectWalletFromPrivateKey(await Deno.readTextFile("./key.sk")); - -const validator = await readValidator(); - -// --- Supporting functions - -async function readValidator(): Promise { - const validator = JSON - .parse(await Deno.readTextFile("plutus.json")) - .validators[0]; - - return { - type: "PlutusV2", - script: toHex(cbor.encode(fromHex(validator.compiledCode))), - }; -} - -const publicKeyHash = lucid.utils - .getAddressDetails(await lucid.wallet.address()) - .paymentCredential - ?.hash; - -const datum = Data.to(new Constr(0, [publicKeyHash])); - -const txHash = await lock(1000000n, { into: validator, owner: datum }); - -await lucid.awaitTx(txHash); - -console.log(`1 tADA locked into the contract at: - Tx ID: ${txHash} - Datum: ${datum} -`); - -// --- Supporting functions - -async function lock( - lovelace: bigint, - { into, owner }: { into: SpendingValidator; owner: string }, -): Promise { - const contractAddress = lucid.utils.validatorToAddress(into); - - const tx = await lucid - .newTx() - .payToContract(contractAddress, { inline: owner }, { lovelace }) - .complete(); - - const signedTx = await tx - .sign() - .complete(); - - return signedTx.submit(); -} diff --git a/examples/hello_world/hello_world-unlock.ts b/examples/hello_world/hello_world-unlock.ts deleted file mode 100644 index d7a05377..00000000 --- a/examples/hello_world/hello_world-unlock.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - Blockfrost, - Constr, - Data, - fromHex, - Lucid, - OutRef, - Redeemer, - SpendingValidator, - toHex, - TxHash, - utf8ToHex, -} from "https://deno.land/x/lucid@0.8.3/mod.ts"; -import * as cbor from "https://deno.land/x/cbor@v1.4.1/index.js"; - -const lucid = await Lucid.new( - new Blockfrost( - "https://cardano-preview.blockfrost.io/api/v0", - Deno.env.get("BLOCKFROST_API_KEY"), - ), - "Preview", -); - -lucid.selectWalletFromPrivateKey(await Deno.readTextFile("./key.sk")); - -const validator = await readValidator(); - -const utxo: OutRef = { txHash: Deno.args[0], outputIndex: 0 }; - -const redeemer = Data.to(new Constr(0, [utf8ToHex("Hello, World!")])); - -const unlockTxHash = await unlock(utxo, { - from: validator, - using: redeemer, -}); - - -await lucid.awaitTx(unlockTxHash); - -console.log(`1 tADA unlocked from the contract - Tx ID: ${unlockTxHash} - Redeemer: ${redeemer} -`); - -// --- Supporting functions - -async function unlock( - ref: OutRef, - { from, using }: { from: SpendingValidator; using: Redeemer }, -): Promise { - const [utxo] = await lucid.utxosByOutRef([ref]); - - const tx = await lucid - .newTx() - .collectFrom([utxo], using) - .addSigner(await lucid.wallet.address()) - .attachSpendingValidator(from) - .complete(); - - const signedTx = await tx - .sign() - .complete(); - - return signedTx.submit(); -} - -async function readValidator(): Promise { - const validator = JSON - .parse(await Deno.readTextFile("plutus.json")) - .validators[0]; - - return { - type: "PlutusV2", - script: toHex(cbor.encode(fromHex(validator.compiledCode))), - }; -} diff --git a/examples/hello_world/lock.ts b/examples/hello_world/lock.ts new file mode 100644 index 00000000..3c7dd12e --- /dev/null +++ b/examples/hello_world/lock.ts @@ -0,0 +1,39 @@ +import { Asset, deserializeAddress, mConStr0 } from "@meshsdk/core"; +import { getScript, getTxBuilder, wallet } from "./common"; + +async function main() { + // these are the assets we want to lock into the contract + const assets: Asset[] = [ + { + unit: "lovelace", + quantity: "1000000", + }, + ]; + + // get utxo and wallet address + const utxos = await wallet.getUtxos(); + const walletAddress = (await wallet.getUsedAddresses())[0]; + + const { scriptAddr } = getScript(); + + // hash of the public key of the wallet, to be used in the datum + const signerHash = deserializeAddress(walletAddress).pubKeyHash; + + // build transaction with MeshTxBuilder + const txBuilder = getTxBuilder(); + await txBuilder + .txOut(scriptAddr, assets) // send assets to the script address + .txOutDatumHashValue(mConStr0([signerHash])) // provide the datum where `"constructor": 0` + .changeAddress(walletAddress) // send change back to the wallet address + .selectUtxosFrom(utxos) + .setNetwork('preprod') + .complete(); + const unsignedTx = txBuilder.txHex; + + const signedTx = await wallet.signTx(unsignedTx); + const txHash = await wallet.submitTx(signedTx); + console.log(`1 tADA locked into the contract at Tx ID: ${txHash}`); +} + +main(); + diff --git a/examples/hello_world/unlock.ts b/examples/hello_world/unlock.ts new file mode 100644 index 00000000..db2f78ec --- /dev/null +++ b/examples/hello_world/unlock.ts @@ -0,0 +1,57 @@ +import { + deserializeAddress, + mConStr0, + stringToHex, +} from "@meshsdk/core"; +import { getScript, getTxBuilder, getUtxoByTxHash, wallet } from "./common"; + +async function main() { + // get utxo, collateral and address from wallet + const utxos = await wallet.getUtxos(); + const walletAddress = (await wallet.getUsedAddresses())[0]; + const collateral = (await wallet.getCollateral())[0]; + + const { scriptCbor } = getScript(); + + // hash of the public key of the wallet, to be used in the datum + const signerHash = deserializeAddress(walletAddress).pubKeyHash; + // redeemer value to unlock the funds + const message = "Hello, World!"; + + // get the utxo from the script address of the locked funds + const txHashFromDesposit = process.argv[2]; + const scriptUtxo = await getUtxoByTxHash(txHashFromDesposit); + + // build transaction with MeshTxBuilder + const txBuilder = getTxBuilder(); + await txBuilder + .spendingPlutusScript("V3") // we used plutus v3 + .txIn( // spend the utxo from the script address + scriptUtxo.input.txHash, + scriptUtxo.input.outputIndex, + scriptUtxo.output.amount, + scriptUtxo.output.address + ) + .txInScript(scriptCbor) + .txInRedeemerValue(mConStr0([stringToHex(message)])) // provide the required redeemer value `Hello, World!` + .txInDatumValue(mConStr0([signerHash])) // only the owner of the wallet can unlock the funds + .requiredSignerHash(signerHash) + .changeAddress(walletAddress) + .txInCollateral( + collateral.input.txHash, + collateral.input.outputIndex, + collateral.output.amount, + collateral.output.address + ) + .selectUtxosFrom(utxos) + .setNetwork('preprod') + .complete(); + const unsignedTx = txBuilder.txHex; + + const signedTx = await wallet.signTx(unsignedTx); + const txHash = await wallet.submitTx(signedTx); + console.log(`1 tADA unlocked from the contract at Tx ID: ${txHash}`); +} + +main(); +