diff --git a/example/lock/scripts/abi.mjs b/example/lock/scripts/abi.mjs deleted file mode 100644 index 03844613e..000000000 --- a/example/lock/scripts/abi.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { copyFile } from "copy-file"; - -await copyFile( - "../../solidity/artifacts/contracts/private/NotoTrackerERC20.sol/NotoTrackerERC20.json", - "src/abis/NotoTrackerERC20.json" -); diff --git a/example/lock/src/index.ts b/example/lock/src/index.ts deleted file mode 100644 index 840a75374..000000000 --- a/example/lock/src/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -import PaladinClient, { - INotoDomainReceipt, - newGroupSalt, - NotoFactory, - PenteFactory, -} from "@lfdecentralizedtrust-labs/paladin-sdk"; -import { newERC20Tracker } from "./helpers/erc20tracker"; -import { checkDeploy, checkReceipt } from "./util"; -import { randomBytes } from "crypto"; - -const logger = console; - -const paladin1 = new PaladinClient({ - url: "http://127.0.0.1:31548", -}); -const paladin2 = new PaladinClient({ - url: "http://127.0.0.1:31648", -}); -const paladin3 = new PaladinClient({ - url: "http://127.0.0.1:31748", -}); - -async function main(): Promise { - const [cashIssuer] = paladin1.getVerifiers("cashIssuer@node1"); - const [investor1] = paladin2.getVerifiers("investor1@node2"); - const [investor2] = paladin3.getVerifiers("investor2@node3"); - - // Create a Pente privacy group for the issuer only - logger.log("Creating issuer privacy group..."); - const penteFactory = new PenteFactory(paladin1, "pente"); - const issuerGroup = await penteFactory.newPrivacyGroup(cashIssuer, { - group: { - salt: newGroupSalt(), - members: [cashIssuer], - }, - evmVersion: "shanghai", - endorsementType: "group_scoped_identities", - externalCallsEnabled: true, - }); - if (!checkDeploy(issuerGroup)) return false; - - // Deploy private tracker to the issuer privacy group - logger.log("Creating private tracker..."); - const tracker = await newERC20Tracker(issuerGroup, cashIssuer, { - name: "CASH", - symbol: "CASH", - }); - if (!checkDeploy(tracker)) return false; - - // Create a Noto token to represent cash - logger.log("Deploying Noto cash token..."); - const notoFactory = new NotoFactory(paladin1, "noto"); - const notoCash = await notoFactory.newNoto(cashIssuer, { - notary: cashIssuer, - notaryMode: "hooks", - options: { - hooks: { - privateGroup: issuerGroup.group, - publicAddress: issuerGroup.address, - privateAddress: tracker.address, - }, - }, - }); - if (!checkDeploy(notoCash)) return false; - - // Issue some cash - logger.log("Issuing cash to investor1..."); - let receipt = await notoCash.mint(cashIssuer, { - to: investor1, - amount: 1000, - data: "0x", - }); - if (!checkReceipt(receipt)) return false; - - // Lock some tokens - logger.log("Locking cash from investor1..."); - receipt = await notoCash.using(paladin2).lock(investor1, { - amount: 100, - data: "0x", - }); - if (!checkReceipt(receipt)) return false; - receipt = await paladin2.getTransactionReceipt(receipt.id, true); - - let domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined; - const lockId = domainReceipt?.lockInfo?.lockId; - if (lockId === undefined) { - logger.error("No lock ID found in domain receipt"); - return false; - } - - // Prepare unlock operation - logger.log("Preparing unlock to investor2..."); - receipt = await notoCash.using(paladin2).prepareUnlock(investor1, { - lockId, - from: investor1, - recipients: [{ to: investor2, amount: 100 }], - data: "0x", - }); - if (!checkReceipt(receipt)) return false; - receipt = await paladin2.getTransactionReceipt(receipt.id, true); - domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined; - - // Approve unlock operation - logger.log("Delegating lock to investor2..."); - receipt = await notoCash.using(paladin2).delegateLock(investor1, { - lockId, - delegate: await investor2.address(), - data: "0x", - }); - if (!checkReceipt(receipt)) return false; - - // Unlock the tokens - logger.log("Unlocking cash..."); - receipt = await notoCash.using(paladin3).unlockAsDelegate(investor2, { - lockId, - lockedInputs: - domainReceipt?.states.readLockedInputs?.map((s) => s.id) ?? [], - lockedOutputs: - domainReceipt?.states.preparedLockedOutputs?.map((s) => s.id) ?? [], - outputs: domainReceipt?.states.preparedOutputs?.map((s) => s.id) ?? [], - signature: "0x", - data: "0x", - }); - if (!checkReceipt(receipt)) return false; - - return true; -} - -if (require.main === module) { - main() - .then((success: boolean) => { - process.exit(success ? 0 : 1); - }) - .catch((err) => { - console.error("Exiting with uncaught error"); - console.error(err); - process.exit(1); - }); -} diff --git a/example/lock/.gitignore b/example/swap/.gitignore similarity index 100% rename from example/lock/.gitignore rename to example/swap/.gitignore diff --git a/example/lock/.vscode/launch.json b/example/swap/.vscode/launch.json similarity index 100% rename from example/lock/.vscode/launch.json rename to example/swap/.vscode/launch.json diff --git a/example/swap/README.md b/example/swap/README.md new file mode 100644 index 000000000..492805637 --- /dev/null +++ b/example/swap/README.md @@ -0,0 +1,46 @@ +# Example: Atomic Swap + +This example demonstrates an atomic swap scenario on Paladin. It performs a swap between: + +- a cash token implemented with Zeto +- an asset token implemented with Noto, with private hooks implemented on Pente + +## Pre-requisites + +Requires a local 3-node Paladin cluster running on `localhost:31548`, `localhost:31648`, and `localhost:31748`. + +## Run standalone + +Compile [Solidity contracts](../../solidity): + +```shell +cd ../../solidity +npm install +npm run compile +``` + +Build [TypeScript SDK](../../sdk/typescript): + +```shell +cd ../../sdk/typescript +npm install +npm run abi +npm run build +``` + +Run example: + +```shell +npm install +npm run abi +npm run start +``` + +## Run with Gradle + +The following will perform all pre-requisites and then run the example: + +```shell +../../gradlew build +npm run start +``` diff --git a/example/lock/build.gradle b/example/swap/build.gradle similarity index 100% rename from example/lock/build.gradle rename to example/swap/build.gradle diff --git a/example/lock/package-lock.json b/example/swap/package-lock.json similarity index 75% rename from example/lock/package-lock.json rename to example/swap/package-lock.json index 69cee94a2..dbcfd2489 100644 --- a/example/lock/package-lock.json +++ b/example/swap/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@lfdecentralizedtrust-labs/paladin-sdk": "file:../../sdk/typescript" + "@lfdecentralizedtrust-labs/paladin-sdk": "file:../../sdk/typescript", + "ethers": "^6.13.4" }, "devDependencies": { "@types/node": "^22.8.7", @@ -33,6 +34,11 @@ "typescript": "^5.6.3" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -74,6 +80,28 @@ "resolved": "../../sdk/typescript", "link": true }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -131,6 +159,11 @@ "node": ">=0.4.0" } }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -168,6 +201,41 @@ "node": ">=0.3.1" } }, + "node_modules/ethers": { + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -250,6 +318,11 @@ } } }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -266,8 +339,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", @@ -275,6 +347,26 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/example/lock/package.json b/example/swap/package.json similarity index 93% rename from example/lock/package.json rename to example/swap/package.json index 651d23ce0..969d64dda 100644 --- a/example/lock/package.json +++ b/example/swap/package.json @@ -18,6 +18,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "@lfdecentralizedtrust-labs/paladin-sdk": "file:../../sdk/typescript" + "@lfdecentralizedtrust-labs/paladin-sdk": "file:../../sdk/typescript", + "ethers": "^6.13.4" } } diff --git a/example/swap/scripts/abi.mjs b/example/swap/scripts/abi.mjs new file mode 100644 index 000000000..1a9916d01 --- /dev/null +++ b/example/swap/scripts/abi.mjs @@ -0,0 +1,16 @@ +import { copyFile } from "copy-file"; + +await copyFile( + "../../solidity/artifacts/contracts/private/NotoTrackerERC20.sol/NotoTrackerERC20.json", + "src/abis/NotoTrackerERC20.json" +); + +await copyFile( + "../../solidity/artifacts/contracts/shared/Atom.sol/AtomFactory.json", + "src/abis/AtomFactory.json" +); + +await copyFile( + "../../solidity/artifacts/contracts/shared/Atom.sol/Atom.json", + "src/abis/Atom.json" +); diff --git a/example/swap/src/helpers/atom.ts b/example/swap/src/helpers/atom.ts new file mode 100644 index 000000000..63319c906 --- /dev/null +++ b/example/swap/src/helpers/atom.ts @@ -0,0 +1,88 @@ +import PaladinClient, { + PaladinVerifier, + TransactionType, +} from "@lfdecentralizedtrust-labs/paladin-sdk"; +import atomJson from "../abis/Atom.json"; +import atomFactoryJson from "../abis/AtomFactory.json"; + +export interface AtomOperation { + contractAddress: string; + callData: string; +} + +export const newAtomFactory = async ( + paladin: PaladinClient, + from: PaladinVerifier +) => { + const txID = await paladin.sendTransaction({ + type: TransactionType.PUBLIC, + abi: atomFactoryJson.abi, + bytecode: atomFactoryJson.bytecode, + function: "", + from: from.lookup, + data: {}, + }); + const receipt = await paladin.pollForReceipt(txID, 10000); + return receipt?.contractAddress + ? new AtomFactory(paladin, receipt.contractAddress) + : undefined; +}; + +export class AtomFactory { + constructor( + protected paladin: PaladinClient, + public readonly address: string + ) {} + + using(paladin: PaladinClient) { + return new AtomFactory(paladin, this.address); + } + + async create(from: PaladinVerifier, operations: AtomOperation[]) { + const txID = await this.paladin.sendTransaction({ + type: TransactionType.PUBLIC, + abi: atomFactoryJson.abi, + function: "create", + from: from.lookup, + to: this.address, + data: { operations }, + }); + const receipt = await this.paladin.pollForReceipt(txID, 10000); + if (receipt) { + const events = await this.paladin.decodeTransactionEvents( + receipt.transactionHash, + atomFactoryJson.abi, + "" + ); + const deployedEvent = events.find((ev) => + ev.soliditySignature.startsWith("event AtomDeployed") + ); + const atomAddress = deployedEvent?.data.addr; + return atomAddress ? new Atom(this.paladin, atomAddress) : undefined; + } + return undefined; + } +} + +export class Atom { + constructor( + protected paladin: PaladinClient, + public readonly address: string + ) {} + + using(paladin: PaladinClient) { + return new Atom(paladin, this.address); + } + + async execute(from: PaladinVerifier) { + const txID = await this.paladin.sendTransaction({ + type: TransactionType.PUBLIC, + abi: atomJson.abi, + function: "execute", + from: from.lookup, + to: this.address, + data: {}, + }); + return this.paladin.pollForReceipt(txID, 10000); + } +} diff --git a/example/lock/src/helpers/erc20tracker.ts b/example/swap/src/helpers/erc20tracker.ts similarity index 100% rename from example/lock/src/helpers/erc20tracker.ts rename to example/swap/src/helpers/erc20tracker.ts diff --git a/example/swap/src/index.ts b/example/swap/src/index.ts new file mode 100644 index 000000000..91a80d16a --- /dev/null +++ b/example/swap/src/index.ts @@ -0,0 +1,230 @@ +import PaladinClient, { + INotoDomainReceipt, + IPreparedTransaction, + newGroupSalt, + NotoFactory, + PenteFactory, + ZetoFactory, +} from "@lfdecentralizedtrust-labs/paladin-sdk"; +import { ethers } from "ethers"; +import { newAtomFactory } from "./helpers/atom"; +import { newERC20Tracker } from "./helpers/erc20tracker"; +import { checkDeploy, checkReceipt } from "./util"; + +const logger = console; + +const paladin1 = new PaladinClient({ + url: "http://127.0.0.1:31548", +}); +const paladin2 = new PaladinClient({ + url: "http://127.0.0.1:31648", +}); +const paladin3 = new PaladinClient({ + url: "http://127.0.0.1:31748", +}); + +// TODO: eliminate the need for this call +async function encodeZetoTransfer(preparedCashTransfer: IPreparedTransaction) { + const zetoTransferAbi = await paladin3.getStoredABI( + preparedCashTransfer.transaction.abiReference ?? "" + ); + return new ethers.Interface(zetoTransferAbi.abi).encodeFunctionData( + "transfer", + [ + preparedCashTransfer.transaction.data.inputs, + preparedCashTransfer.transaction.data.outputs, + preparedCashTransfer.transaction.data.proof, + preparedCashTransfer.transaction.data.data, + ] + ); +} + +async function main(): Promise { + const [cashIssuer, assetIssuer] = paladin1.getVerifiers( + "cashIssuer@node1", + "assetIssuer@node1" + ); + const [investor1] = paladin2.getVerifiers("investor1@node2"); + const [investor2] = paladin3.getVerifiers("investor2@node3"); + + // Deploy the atom factory on the base ledger + logger.log("Creating atom factory..."); + const atomFactory = await newAtomFactory(paladin1, cashIssuer); + if (!checkDeploy(atomFactory)) return false; + + // Deploy a Zeto token to represent cash + logger.log("Deploying Zeto cash token..."); + const zetoFactory = new ZetoFactory(paladin1, "zeto"); + const zetoCash = await zetoFactory.newZeto(cashIssuer, { + tokenName: "Zeto_Anon", + }); + if (!checkDeploy(zetoCash)) return false; + + // Create a Pente privacy group for the asset issuer only + logger.log("Creating asset issuer privacy group..."); + const penteFactory = new PenteFactory(paladin1, "pente"); + const issuerGroup = await penteFactory.newPrivacyGroup(assetIssuer, { + group: { + salt: newGroupSalt(), + members: [assetIssuer], + }, + evmVersion: "shanghai", + endorsementType: "group_scoped_identities", + externalCallsEnabled: true, + }); + if (!checkDeploy(issuerGroup)) return false; + + // Deploy private tracker to the issuer privacy group + logger.log("Creating private asset tracker..."); + const tracker = await newERC20Tracker(issuerGroup, assetIssuer, { + name: "ASSET", + symbol: "ASSET", + }); + if (!checkDeploy(tracker)) return false; + + // Create a Noto token to represent an asset + logger.log("Deploying Noto asset token..."); + const notoFactory = new NotoFactory(paladin1, "noto"); + const notoAsset = await notoFactory.newNoto(assetIssuer, { + notary: assetIssuer, + notaryMode: "hooks", + options: { + hooks: { + privateGroup: issuerGroup.group, + publicAddress: issuerGroup.address, + privateAddress: tracker.address, + }, + }, + }); + if (!checkDeploy(notoAsset)) return false; + + // Issue asset + logger.log("Issuing asset to investor1..."); + let receipt = await notoAsset.mint(assetIssuer, { + to: investor1, + amount: 1000, + data: "0x", + }); + if (!checkReceipt(receipt)) return false; + + // Issue cash + logger.log("Issuing cash to investor2..."); + receipt = await zetoCash.mint(cashIssuer, { + mints: [ + { + to: investor2, + amount: 10000, + }, + ], + }); + if (!checkReceipt(receipt)) return false; + + // Lock the asset for the swap + logger.log("Locking asset from investor1..."); + receipt = await notoAsset.using(paladin2).lock(investor1, { + amount: 100, + data: "0x", + }); + if (!checkReceipt(receipt)) return false; + receipt = await paladin2.getTransactionReceipt(receipt.id, true); + + let domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined; + const lockId = domainReceipt?.lockInfo?.lockId; + if (lockId === undefined) { + logger.error("No lock ID found in domain receipt"); + return false; + } + + // Prepare asset unlock operation + logger.log("Preparing unlock to investor2..."); + receipt = await notoAsset.using(paladin2).prepareUnlock(investor1, { + lockId, + from: investor1, + recipients: [{ to: investor2, amount: 100 }], + data: "0x", + }); + if (!checkReceipt(receipt)) return false; + receipt = await paladin2.getTransactionReceipt(receipt.id, true); + + domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined; + const encodedAssetTransfer = domainReceipt?.lockInfo?.unlock; + if (encodedAssetTransfer === undefined) { + logger.error("No unlock data found in domain receipt"); + return false; + } + + // Prepare cash transfer + logger.log("Preparing cash transfer to investor1..."); + const txID = await zetoCash.using(paladin3).prepareTransfer(investor2, { + transfers: [ + { + to: investor1, + amount: 100, + }, + ], + }); + const preparedCashTransfer = await paladin3.pollForPreparedTransaction( + txID, + 10000 + ); + if (!preparedCashTransfer) return false; + + const encodedCashTransfer = await encodeZetoTransfer(preparedCashTransfer); + + logger.log("Locking transfer proof..."); + receipt = await zetoCash.using(paladin3).lock(investor2, { + call: encodedCashTransfer, + delegate: await investor2.address(), + }); + if (!checkReceipt(receipt)) return false; + + // Create an atom for the swap + logger.log("Creating atom..."); + const atom = await atomFactory.create(cashIssuer, [ + { + contractAddress: notoAsset.address, + callData: encodedAssetTransfer, + }, + { + contractAddress: zetoCash.address, + callData: encodedCashTransfer, + }, + ]); + if (!checkDeploy(atom)) return false; + + // Approve asset unlock operation + logger.log("Approving asset leg..."); + receipt = await notoAsset.using(paladin2).delegateLock(investor1, { + lockId, + delegate: atom.address, + data: "0x", + }); + if (!checkReceipt(receipt)) return false; + + // Approve cash transfer operation + logger.log("Approving cash leg..."); + receipt = await zetoCash.using(paladin3).lock(investor2, { + call: encodedCashTransfer, + delegate: atom.address, + }); + if (!checkReceipt(receipt)) return false; + + // Unlock the asset + logger.log("Performing swap..."); + receipt = await atom.using(paladin3).execute(investor2); + if (!checkReceipt(receipt)) return false; + + return true; +} + +if (require.main === module) { + main() + .then((success: boolean) => { + process.exit(success ? 0 : 1); + }) + .catch((err) => { + console.error("Exiting with uncaught error"); + console.error(err); + process.exit(1); + }); +} diff --git a/example/lock/src/util.ts b/example/swap/src/util.ts similarity index 100% rename from example/lock/src/util.ts rename to example/swap/src/util.ts diff --git a/example/lock/tsconfig.json b/example/swap/tsconfig.json similarity index 100% rename from example/lock/tsconfig.json rename to example/swap/tsconfig.json diff --git a/sdk/typescript/src/domains/noto.ts b/sdk/typescript/src/domains/noto.ts index 9793182f8..b8637fd68 100644 --- a/sdk/typescript/src/domains/noto.ts +++ b/sdk/typescript/src/domains/noto.ts @@ -342,4 +342,15 @@ export class NotoInstance { }); return this.paladin.pollForReceipt(txID, this.options.pollTimeout); } + + encodeUnlock(data: NotoUnlockPublicParams) { + return new ethers.Interface(notoJSON.abi).encodeFunctionData("unlock", [ + data.lockId, + data.lockedInputs, + data.lockedOutputs, + data.outputs, + data.signature, + data.data, + ]); + } } diff --git a/sdk/typescript/src/interfaces/transaction.ts b/sdk/typescript/src/interfaces/transaction.ts index 50345832d..e2914456b 100644 --- a/sdk/typescript/src/interfaces/transaction.ts +++ b/sdk/typescript/src/interfaces/transaction.ts @@ -160,3 +160,8 @@ export interface IEventWithData { address: string; data: any; } + +export interface IStoredABI { + hash: string; + abi: ethers.InterfaceAbi; +} diff --git a/sdk/typescript/src/paladin.ts b/sdk/typescript/src/paladin.ts index 52e8f75f5..245ff87f9 100644 --- a/sdk/typescript/src/paladin.ts +++ b/sdk/typescript/src/paladin.ts @@ -15,6 +15,7 @@ import { ITransactionStates, IDecodedEvent, IEventWithData, + IStoredABI, } from "./interfaces/transaction"; import { Algorithms, @@ -208,6 +209,13 @@ export default class PaladinClient { await this.post("ptx_storeABI", [abi]); } + async getStoredABI(hash: string) { + const res = await this.post>("ptx_getStoredABI", [ + hash, + ]); + return res.data.result; + } + async decodeEvent(topics: string[], data: string) { try { const res = await this.post>( diff --git a/settings.gradle b/settings.gradle index d44608d83..146352dc7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,7 +8,7 @@ include 'domains:zeto' include 'domains:pente' include 'domains:integration-test' include 'example:bond' -include 'example:lock' +include 'example:swap' include 'example:zeto' include 'operator' include 'sdk:typescript'