From af6f1fd1c659afea0a8b53eeb6b770258d0aa097 Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Wed, 4 Sep 2024 17:41:33 +0200 Subject: [PATCH 1/9] docs(deposit): added example demonstrating the use of allocation with deposit --- examples/package.json | 3 +- .../rental-model/advanced/deposit/config.ts | 14 + .../advanced/deposit/contracts/glmAbi.json | 470 +++++++++++++ .../advanced/deposit/contracts/lockAbi.json | 631 ++++++++++++++++++ .../rental-model/advanced/deposit/funder.ts | 211 ++++++ .../rental-model/advanced/deposit/index.ts | 103 +++ .../rental-model/advanced/deposit/observer.ts | 67 ++ 7 files changed, 1498 insertions(+), 1 deletion(-) create mode 100644 examples/rental-model/advanced/deposit/config.ts create mode 100644 examples/rental-model/advanced/deposit/contracts/glmAbi.json create mode 100644 examples/rental-model/advanced/deposit/contracts/lockAbi.json create mode 100644 examples/rental-model/advanced/deposit/funder.ts create mode 100644 examples/rental-model/advanced/deposit/index.ts create mode 100644 examples/rental-model/advanced/deposit/observer.ts diff --git a/examples/package.json b/examples/package.json index d94076c14..17f687484 100644 --- a/examples/package.json +++ b/examples/package.json @@ -38,7 +38,8 @@ "@golem-sdk/pino-logger": "^1.0.2", "commander": "^12.0.0", "express": "^4.18.2", - "tsx": "^4.7.1" + "tsx": "^4.7.1", + "viem": "^2.21.1" }, "devDependencies": { "@types/node": "20", diff --git a/examples/rental-model/advanced/deposit/config.ts b/examples/rental-model/advanced/deposit/config.ts new file mode 100644 index 000000000..62ae6cd3b --- /dev/null +++ b/examples/rental-model/advanced/deposit/config.ts @@ -0,0 +1,14 @@ +export default { + funder: { + address: "0x00", + privateKey: "0x00", + nonceSpace: 1000000, + }, + rpcUrl: "https://holesky.rpc-node.dev.golem.network", + lockPaymentContract: { + holeskyAddress: "0x63704675f72A47a7a183112700Cb48d4B0A94332", + }, + glmContract: { + holeskyAddress: "0x8888888815bf4DB87e57B609A50f938311EEd068", + }, +}; diff --git a/examples/rental-model/advanced/deposit/contracts/glmAbi.json b/examples/rental-model/advanced/deposit/contracts/glmAbi.json new file mode 100644 index 000000000..68f507842 --- /dev/null +++ b/examples/rental-model/advanced/deposit/contracts/glmAbi.json @@ -0,0 +1,470 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "account", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "holder", + "type": "address" + }, + { + "name": "spender", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "expiry", + "type": "uint256" + }, + { + "name": "allowed", + "type": "bool" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceMinter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "recipient", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "account", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "_migrationAgent", + "type": "address" + }, + { + "name": "_chainId", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } +] diff --git a/examples/rental-model/advanced/deposit/contracts/lockAbi.json b/examples/rental-model/advanced/deposit/contracts/lockAbi.json new file mode 100644 index 000000000..bbe8108d4 --- /dev/null +++ b/examples/rental-model/advanced/deposit/contracts/lockAbi.json @@ -0,0 +1,631 @@ +[ + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_GLM", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "DepositClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "DepositCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "DepositExtended", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "DepositFeeTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "DepositTerminated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "DepositTransfer", + "type": "event" + }, + { + "inputs": [], + "name": "CONTRACT_ID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_ID_AND_VERSION", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_VERSION", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GLM", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "closeDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "flatFeeAmount", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "validToTimestamp", + "type": "uint64" + } + ], + "name": "createDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "depositSingleTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "depositSingleTransferAndClose", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "payments", + "type": "bytes32[]" + } + ], + "name": "depositTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "payments", + "type": "bytes32[]" + } + ], + "name": "depositTransferAndClose", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "validTo", + "type": "uint64" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "feeAmount", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "uint128", + "name": "additionalAmount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "additionalFlatFee", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "validToTimestamp", + "type": "uint64" + } + ], + "name": "extendDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "funderFromId", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getDeposit", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "validTo", + "type": "uint64" + } + ], + "internalType": "struct ILockPayment.DepositView", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + } + ], + "name": "getDepositByNonce", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "validTo", + "type": "uint64" + } + ], + "internalType": "struct ILockPayment.DepositView", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidateDepositSignature", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "idFromNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "internalType": "address", + "name": "funder", + "type": "address" + } + ], + "name": "idFromNonceAndFunder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "nonceFromId", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "name": "terminateDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "flatFeeAmount", + "type": "uint128" + } + ], + "name": "validateDeposit", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/examples/rental-model/advanced/deposit/funder.ts b/examples/rental-model/advanced/deposit/funder.ts new file mode 100644 index 000000000..7b9898955 --- /dev/null +++ b/examples/rental-model/advanced/deposit/funder.ts @@ -0,0 +1,211 @@ +import { Address, createPublicClient, createWalletClient, formatEther, Hex, http, parseEther } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { holesky } from "viem/chains"; +import chalk from "chalk"; +import { readFile } from "fs/promises"; +import config from "./config.js"; + +const abiGlm = JSON.parse(await readFile("./contracts/glmAbi.json", "utf8")); +const abiLock = JSON.parse(await readFile("./contracts/lockAbi.json", "utf8")); +const funderAccount = privateKeyToAccount(config.funder.privateKey); + +// walletClient for writeContract functions +const walletClient = createWalletClient({ + account: funderAccount, + chain: holesky, + transport: http(config.rpcUrl), +}); + +// publicClient for readContract functions +const publicClient = createPublicClient({ + chain: holesky, + transport: http(config.rpcUrl), +}); + +const LOCK_CONTRACT = { + address: config.lockPaymentContract.holeskyAddress, + abi: abiLock, +}; +const GLM_CONTRACT = { + address: config.glmContract.holeskyAddress, + abi: abiGlm, +}; + +const nonce = Math.floor(Math.random() * config.funder.nonceSpace); + +async function createAllowance({ budget, fee }: { budget: number; fee: number }) { + const amountWei = parseEther(`${budget}`); + const flatFeeAmountWei = parseEther(`${fee}`); + const allowanceBudget = amountWei + flatFeeAmountWei; + + console.log( + chalk.blue(`\nCreating allowance of ${formatEther(allowanceBudget)} GLM for ${LOCK_CONTRACT.address} contract ...`), + ); + + const hash = await walletClient.writeContract({ + address: GLM_CONTRACT.address, + abi: GLM_CONTRACT.abi, + functionName: "increaseAllowance", + args: [LOCK_CONTRACT.address, allowanceBudget], + chain: walletClient.chain, + account: walletClient.account, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash, + }); + + console.log(chalk.blue(`Allowance successfully created with Tx ${receipt.transactionHash}.`)); +} + +async function checkAllowance() { + const args = [config.funder.address, LOCK_CONTRACT.address]; + + console.log(chalk.blue(`\nChecking allowance for ${args[1]} contract ...`)); + + const allowance = await publicClient.readContract({ + abi: GLM_CONTRACT.abi, + functionName: "allowance", + address: GLM_CONTRACT.address, + args, + }); + + console.log(chalk.blue(`Allowance of ${formatEther(allowance)} GLM is set.`)); +} + +type DepositParams = { + address: string; + budget: number; + fee: number; + expirationSec: number; +}; + +async function createDeposit({ address, budget, fee, expirationSec }: DepositParams) { + const validToTimestamp = new Date().getTime() + expirationSec * 1000; + const args = [BigInt(nonce), address, parseEther(`${budget}`), parseEther(`${fee}`), BigInt(validToTimestamp)]; + + console.log( + chalk.grey( + `\nCreating deposit of amount: ${formatEther(args[2])} GLM, flatFeeAmount: ${formatEther(args[3])} GLM, for ${(expirationSec / 3600).toFixed(2)} hours.`, + ), + ); + console.log(chalk.grey(`Using contract at address: ${LOCK_CONTRACT.address}.`)); + + const hash = await walletClient.writeContract({ + address: LOCK_CONTRACT.address, + abi: LOCK_CONTRACT.abi, + functionName: "createDeposit", + args, + chain: walletClient.chain, + account: walletClient.account, + }); + + await publicClient.waitForTransactionReceipt({ + hash, + }); + + console.log(chalk.grey(`Deposit successfully created with Tx ${hash}.`)); + + const depositId = await getDepositID(); + const depositDetails = await getDepositDetails(); + return { + id: "0x" + depositId.toString(16), + amount: depositDetails.amount, + contract: config.lockPaymentContract.holeskyAddress, + }; +} + +async function extendDeposit({ budget, fee, expirationSec }: Partial & { expirationSec: number }) { + const validToTimestamp = new Date().getTime() + expirationSec * 1000; + const args = [ + BigInt(nonce), + BigInt(budget ? parseEther(`${budget}`) : 0), + BigInt(fee ? parseEther(`${fee}`) : 0), + BigInt(validToTimestamp), + ]; + + console.log( + chalk.grey( + `\nExtending deposit of additional amount: ${formatEther(args[1])} GLM, flatFeeAmount: ${formatEther(args[2])} GLM, for ${(expirationSec / 3600).toFixed(2)} hours.`, + ), + ); + console.log(chalk.grey(`Using contract at address: ${LOCK_CONTRACT.address}.`)); + + const hash = await walletClient.writeContract({ + abi: LOCK_CONTRACT.abi, + functionName: "extendDeposit", + address:
LOCK_CONTRACT.address, + args, + chain: walletClient.chain, + account: walletClient.account, + }); + + await publicClient.waitForTransactionReceipt({ + hash, + }); + + console.log(chalk.grey(`Deposit successfully extended with Tx ${hash}.`)); +} + +async function getDepositID() { + const depositID = await publicClient.readContract({ + address:
LOCK_CONTRACT.address, + abi: LOCK_CONTRACT.abi, + functionName: "idFromNonceAndFunder", + args: [BigInt(nonce), config.funder.address], + }); + + console.log(chalk.grey(`\nDepositID: ${depositID} available on contract at address: ${LOCK_CONTRACT.address}.`)); + return depositID; +} + +interface DepositData { + amount: bigint; + id: string; +} + +async function getDepositDetails() { + const deposit = await publicClient.readContract({ + address:
LOCK_CONTRACT.address, + abi: LOCK_CONTRACT.abi, + functionName: "getDepositByNonce", + args: [BigInt(nonce), config.funder.address], + }); + + console.log(chalk.grey(`\nDeposit of `), deposit, chalk.grey(` available on contract ${LOCK_CONTRACT.address}.`)); + const depositData = { + amount: formatEther(deposit.amount), + id: deposit.id.toString(), + contract: LOCK_CONTRACT.address, + }; + return depositData; +} + +export const clearAllowance = async () => { + const args = [LOCK_CONTRACT.address, BigInt(0)]; + + console.log(chalk.yellow(`\nClearing allowance for ${args[0]} contract ...`)); + + const hash = await walletClient.writeContract({ + abi: GLM_CONTRACT.abi, + functionName: "approve", + address:
GLM_CONTRACT.address, + args, + chain: walletClient.chain, + account: walletClient.account, + }); + + await publicClient.waitForTransactionReceipt({ + hash, + }); + + console.log(chalk.yellow(`Allowance cleared with Tx ${hash}.\n`)); +}; + +export default { + createAllowance, + checkAllowance, + createDeposit, + extendDeposit, + clearAllowance, +}; diff --git a/examples/rental-model/advanced/deposit/index.ts b/examples/rental-model/advanced/deposit/index.ts new file mode 100644 index 000000000..e4e477b41 --- /dev/null +++ b/examples/rental-model/advanced/deposit/index.ts @@ -0,0 +1,103 @@ +import funder from "./funder"; +import observer from "./observer"; +import { GolemNetwork, MarketOrderSpec, waitFor } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; +import chalk from "chalk"; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ level: "info" }), + }); + + try { + await glm.connect(); + + // const spenderAddress = glm.getIdentity().identity; TODO + const spenderAddress = "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119"; + const budget = 1.0; + const fee = 0.5; + const expirationSec = 60 * 60; // 1 hour + + // In order for a founder to create a deposit, he must first create an allowance. + await funder.createAllowance({ budget, fee }); + // Check if allowance was created correctly + await funder.checkAllowance(); + + const deposit = await funder.createDeposit({ address: spenderAddress, budget, fee, expirationSec }); + // The funder can also extend the deposit, for example by extending expiration time + await funder.extendDeposit({ expirationSec: expirationSec + 5 * 60 }); // extend time by 5 min + + // After the deposit is properly prepared, the funder can clear the allowance + await funder.clearAllowance(); + + const allocation = await glm.payment.createAllocation({ + deposit, + budget, + expirationSec, + }); + + const { stopWatchingContractTransactions, isDepositClosed } = + await observer.startWatchingContractTransactions(spenderAddress); + + const order1: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "burn-rate", + avgGlmPerHour: 0.5, + }, + }, + payment: { + allocation, + }, + }; + + const order2: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + rentHours: 0.5, + pricing: { + model: "burn-rate", + avgGlmPerHour: 0.5, + }, + }, + payment: { + allocation: allocation.id, // alternative way to pass allocation ID + }, + }; + + const rental1 = await glm.oneOf({ order: order1 }); + + await rental1 + .getExeUnit() + .then((exe) => exe.run(`echo Task 1 running on ${exe.provider.name}`)) + .then((res) => console.log(chalk.inverse("\n", res.stdout))); + + await rental1.stopAndFinalize(); + + const rental2 = await glm.oneOf({ order: order2 }); + + await rental2 + .getExeUnit() + .then((exe) => exe.run(`echo Task 2 Running on ${exe.provider.name}`)) + .then((res) => console.log(chalk.inverse("\n", res.stdout))); + + await rental2.stopAndFinalize(); + + // Once the spender releases the allocation, the deposit will be closed + // and you will not be able to use it again. + await glm.payment.releaseAllocation(allocation); + + await waitFor(isDepositClosed); + stopWatchingContractTransactions(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/rental-model/advanced/deposit/observer.ts b/examples/rental-model/advanced/deposit/observer.ts new file mode 100644 index 000000000..c57d6b83a --- /dev/null +++ b/examples/rental-model/advanced/deposit/observer.ts @@ -0,0 +1,67 @@ +import { Address, createPublicClient, decodeFunctionData, Hex, http, Log, parseAbi } from "viem"; +import config from "./config.js"; +import { holesky } from "viem/chains"; +import { readFile } from "fs/promises"; +import chalk from "chalk"; + +const abiLock = JSON.parse(await readFile("./contracts/lockAbi.json", "utf8")); + +const publicClient = createPublicClient({ + chain: holesky, + transport: http(config.rpcUrl), +}); + +async function checkIsDopositClosed(spenderAddress: Address, funderAddress: Address, logs: Log[]) { + for (const log of logs) { + if (!log.transactionHash) return false; + const transaction = await publicClient.getTransaction({ hash: log.transactionHash }); + const parsedMethod = decodeFunctionData({ + abi: abiLock, + data: transaction.input, + }); + const functionNamePlusArgs = `${parsedMethod.functionName}(${parsedMethod.args.join(",")})`; + console.log(chalk.magenta("\ncall:", functionNamePlusArgs)); + console.log(chalk.magenta("event:"), log["eventName"]); + console.log(chalk.magenta("from:"), transaction.from); + console.log(chalk.magenta("hash:"), transaction.hash, "\n"); + const functionName = parsedMethod.functionName.toLowerCase(); + // if deposit is closed by spender (requestor), stop observing + if (functionName.includes("close") && transaction.from === spenderAddress) { + console.log(chalk.grey(`Deposit has been closed by spender.`)); + return true; + } + // if deposit is terminated by spender (requestor), stop observing + if (functionName === "terminatedeposit" && transaction.from === funderAddress) { + console.log(chalk.grey(`Deposit has been terminated by spender.`)); + return true; + } + } + return false; +} + +async function startWatchingContractTransactions(address: string) { + const spenderAddress =
address; + const funderAddress =
config.funder.address; + let isDepositClosed = false; + + const stopWatchingContractTransactions = publicClient.watchEvent({ + onLogs: async (logs) => (isDepositClosed = await checkIsDopositClosed(spenderAddress, funderAddress, logs)), + events: parseAbi([ + "event DepositCreated(uint256 indexed id, address spender)", + "event DepositClosed(uint256 indexed id, address spender)", + "event DepositExtended(uint256 indexed id, address spender)", + "event DepositFeeTransfer(uint256 indexed id, address spender, uint128 amount)", + "event DepositTerminated(uint256 indexed id, address spender)", + "event DepositTransfer(uint256 indexed id, address spender, address recipient, uint128 amount)", + ]), + address:
config.lockPaymentContract.holeskyAddress, + }); + return { + stopWatchingContractTransactions, + isDepositClosed: () => isDepositClosed, + }; +} + +export default { + startWatchingContractTransactions, +}; From 6b79c6a0388d2917fc1fda540406d2927f3f0aff Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Wed, 4 Sep 2024 17:46:23 +0200 Subject: [PATCH 2/9] build(dev-deps): added viem to dev-deps --- package-lock.json | 168 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 +- 2 files changed, 166 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index aca67db04..52121a89b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ ], "dependencies": { "@golem-sdk/pino-logger": "^1.1.0", - "@rollup/rollup-win32-x64-msvc": "^4", "async-lock": "^1.4.1", "async-retry": "^1.3.3", "axios": "^1.6.7", @@ -83,7 +82,8 @@ "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", "typedoc-theme-hierarchy": "4.1.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "viem": "^2.21.1" }, "engines": { "node": ">=18.0.0" @@ -103,7 +103,8 @@ "@golem-sdk/pino-logger": "^1.0.2", "commander": "^12.0.0", "express": "^4.18.2", - "tsx": "^4.7.1" + "tsx": "^4.7.1", + "viem": "^2.21.1" }, "devDependencies": { "@types/node": "20", @@ -123,6 +124,11 @@ "node": ">=0.10.0" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "dev": true, @@ -2402,6 +2408,28 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -3118,6 +3146,39 @@ "win32" ] }, + "node_modules/@scure/base": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", + "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@semantic-release/commit-analyzer": { "version": "12.0.0", "dev": true, @@ -4310,6 +4371,26 @@ "dev": true, "license": "ISC" }, + "node_modules/abitype": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", + "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -8889,6 +8970,20 @@ "dev": true, "license": "ISC" }, + "node_modules/isows": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", + "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isstream": { "version": "0.1.2", "dev": true, @@ -18403,7 +18498,7 @@ }, "node_modules/typescript": { "version": "5.4.3", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18666,6 +18761,56 @@ "extsprintf": "^1.2.0" } }, + "node_modules/viem": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.1.tgz", + "integrity": "sha512-nlIc2LLS6aqkngULS9UJ2Sg3nHKAgF9bbpDUwjUoAUBijd69mrCWPBXQ8jmbzcx12uZUfd9Nc//CHgSVZiMwyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.4.0", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0", + "abitype": "1.0.5", + "isows": "1.0.4", + "webauthn-p256": "0.0.5", + "ws": "8.17.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/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/vscode-oniguruma": { "version": "1.7.0", "dev": true, @@ -18698,6 +18843,21 @@ "node": ">=10.13.0" } }, + "node_modules/webauthn-p256": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.5.tgz", + "integrity": "sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, "node_modules/webpack": { "version": "5.91.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", diff --git a/package.json b/package.json index 72ca267f1..60b4a0cd4 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,8 @@ "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", "typedoc-theme-hierarchy": "4.1.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "viem": "^2.21.1" }, "optionalDependencies": { "@rollup/rollup-darwin-x64": "^4", From 38bbb78468fabe66bad167e0bb8e9116702a90aa Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Thu, 5 Sep 2024 11:46:16 +0200 Subject: [PATCH 3/9] chore: added comments to deposit example --- .../rental-model/advanced/deposit/funder.ts | 39 +++++++++++-------- .../rental-model/advanced/deposit/index.ts | 37 +++++++++++------- .../rental-model/advanced/deposit/observer.ts | 17 ++++---- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/examples/rental-model/advanced/deposit/funder.ts b/examples/rental-model/advanced/deposit/funder.ts index 7b9898955..61baa0b40 100644 --- a/examples/rental-model/advanced/deposit/funder.ts +++ b/examples/rental-model/advanced/deposit/funder.ts @@ -8,6 +8,7 @@ import config from "./config.js"; const abiGlm = JSON.parse(await readFile("./contracts/glmAbi.json", "utf8")); const abiLock = JSON.parse(await readFile("./contracts/lockAbi.json", "utf8")); const funderAccount = privateKeyToAccount(config.funder.privateKey); +const nonce = Math.floor(Math.random() * config.funder.nonceSpace); // walletClient for writeContract functions const walletClient = createWalletClient({ @@ -31,15 +32,15 @@ const GLM_CONTRACT = { abi: abiGlm, }; -const nonce = Math.floor(Math.random() * config.funder.nonceSpace); - async function createAllowance({ budget, fee }: { budget: number; fee: number }) { const amountWei = parseEther(`${budget}`); const flatFeeAmountWei = parseEther(`${fee}`); const allowanceBudget = amountWei + flatFeeAmountWei; console.log( - chalk.blue(`\nCreating allowance of ${formatEther(allowanceBudget)} GLM for ${LOCK_CONTRACT.address} contract ...`), + chalk.yellow( + `\nCreating allowance of ${formatEther(allowanceBudget)} GLM for ${LOCK_CONTRACT.address} contract ...`, + ), ); const hash = await walletClient.writeContract({ @@ -55,13 +56,13 @@ async function createAllowance({ budget, fee }: { budget: number; fee: number }) hash, }); - console.log(chalk.blue(`Allowance successfully created with Tx ${receipt.transactionHash}.`)); + console.log(chalk.yellow(`Allowance successfully created with Tx ${receipt.transactionHash}.`)); } async function checkAllowance() { const args = [config.funder.address, LOCK_CONTRACT.address]; - console.log(chalk.blue(`\nChecking allowance for ${args[1]} contract ...`)); + console.log(chalk.yellow(`\nChecking allowance for ${args[1]} contract ...`)); const allowance = await publicClient.readContract({ abi: GLM_CONTRACT.abi, @@ -70,7 +71,7 @@ async function checkAllowance() { args, }); - console.log(chalk.blue(`Allowance of ${formatEther(allowance)} GLM is set.`)); + console.log(chalk.yellow(`Allowance of ${formatEther(allowance)} GLM is set.`)); } type DepositParams = { @@ -85,11 +86,11 @@ async function createDeposit({ address, budget, fee, expirationSec }: DepositPar const args = [BigInt(nonce), address, parseEther(`${budget}`), parseEther(`${fee}`), BigInt(validToTimestamp)]; console.log( - chalk.grey( + chalk.blueBright( `\nCreating deposit of amount: ${formatEther(args[2])} GLM, flatFeeAmount: ${formatEther(args[3])} GLM, for ${(expirationSec / 3600).toFixed(2)} hours.`, ), ); - console.log(chalk.grey(`Using contract at address: ${LOCK_CONTRACT.address}.`)); + console.log(chalk.blueBright(`Using contract at address: ${LOCK_CONTRACT.address}.`)); const hash = await walletClient.writeContract({ address: LOCK_CONTRACT.address, @@ -104,7 +105,7 @@ async function createDeposit({ address, budget, fee, expirationSec }: DepositPar hash, }); - console.log(chalk.grey(`Deposit successfully created with Tx ${hash}.`)); + console.log(chalk.blueBright(`Deposit successfully created with Tx ${hash}.`)); const depositId = await getDepositID(); const depositDetails = await getDepositDetails(); @@ -125,11 +126,11 @@ async function extendDeposit({ budget, fee, expirationSec }: Partialargs[1])} GLM, flatFeeAmount: ${formatEther(args[2])} GLM, for ${(expirationSec / 3600).toFixed(2)} hours.`, ), ); - console.log(chalk.grey(`Using contract at address: ${LOCK_CONTRACT.address}.`)); + console.log(chalk.blueBright(`Using contract at address: ${LOCK_CONTRACT.address}.`)); const hash = await walletClient.writeContract({ abi: LOCK_CONTRACT.abi, @@ -144,7 +145,7 @@ async function extendDeposit({ budget, fee, expirationSec }: Partial { +async function clearAllowance() { const args = [LOCK_CONTRACT.address, BigInt(0)]; console.log(chalk.yellow(`\nClearing allowance for ${args[0]} contract ...`)); @@ -200,7 +207,7 @@ export const clearAllowance = async () => { }); console.log(chalk.yellow(`Allowance cleared with Tx ${hash}.\n`)); -}; +} export default { createAllowance, diff --git a/examples/rental-model/advanced/deposit/index.ts b/examples/rental-model/advanced/deposit/index.ts index e4e477b41..4fa0fa1dd 100644 --- a/examples/rental-model/advanced/deposit/index.ts +++ b/examples/rental-model/advanced/deposit/index.ts @@ -1,8 +1,19 @@ +/** + * In this example we demonstrate executing tasks on a golem but using funds deposited by another person. + * In this example, it is called Funder. The funder is responsible for allocating the deposit, + * which will then be used by the Spender (requestor) to create an allocation for a payment. + * + * To run the example, it is necessary to define the funder's address in the config.ts file and a private key + * that will allow depositing specific funds on the contract. + * + * In order to check if everything went correctly, the observer object logs transaction information + * in the smart contract and the script waits for confirmation on the blockchain until the deposit is closed. + */ + import funder from "./funder"; import observer from "./observer"; import { GolemNetwork, MarketOrderSpec, waitFor } from "@golem-sdk/golem-js"; import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; -import chalk from "chalk"; (async () => { const glm = new GolemNetwork({ @@ -12,8 +23,7 @@ import chalk from "chalk"; try { await glm.connect(); - // const spenderAddress = glm.getIdentity().identity; TODO - const spenderAddress = "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119"; + const { identity: spenderAddress } = await glm.services.yagna.identity.getIdentity(); const budget = 1.0; const fee = 0.5; const expirationSec = 60 * 60; // 1 hour @@ -30,14 +40,15 @@ import chalk from "chalk"; // After the deposit is properly prepared, the funder can clear the allowance await funder.clearAllowance(); + // Now the Spender (requestor) can use the deposit to create an allocation const allocation = await glm.payment.createAllocation({ deposit, budget, expirationSec, }); - const { stopWatchingContractTransactions, isDepositClosed } = - await observer.startWatchingContractTransactions(spenderAddress); + // We are starting the contract transaction observations for the spender address + const observation = await observer.startWatchingContractTransactions(spenderAddress); const order1: MarketOrderSpec = { demand: { @@ -75,8 +86,8 @@ import chalk from "chalk"; await rental1 .getExeUnit() - .then((exe) => exe.run(`echo Task 1 running on ${exe.provider.name}`)) - .then((res) => console.log(chalk.inverse("\n", res.stdout))); + .then((exe) => exe.run(`echo Task 1 running on provider ${exe.provider.name} 👽`)) + .then((res) => console.log(res.stdout)); await rental1.stopAndFinalize(); @@ -84,17 +95,17 @@ import chalk from "chalk"; await rental2 .getExeUnit() - .then((exe) => exe.run(`echo Task 2 Running on ${exe.provider.name}`)) - .then((res) => console.log(chalk.inverse("\n", res.stdout))); + .then((exe) => exe.run(`echo Task 2 Running on provider ${exe.provider.name} 🤠`)) + .then((res) => console.log(res.stdout)); await rental2.stopAndFinalize(); - // Once the spender releases the allocation, the deposit will be closed - // and you will not be able to use it again. + // Once Spender releases the allocation, the deposit will be closed and cannot be used again. await glm.payment.releaseAllocation(allocation); - await waitFor(isDepositClosed); - stopWatchingContractTransactions(); + // We wait (max 2 mins) for confirmation from blockchain + await waitFor(observation.isDepositClosed, { abortSignal: AbortSignal.timeout(120_000) }); + observation.stopWatchingContractTransactions(); } catch (err) { console.error("Failed to run the example", err); } finally { diff --git a/examples/rental-model/advanced/deposit/observer.ts b/examples/rental-model/advanced/deposit/observer.ts index c57d6b83a..2957f68cc 100644 --- a/examples/rental-model/advanced/deposit/observer.ts +++ b/examples/rental-model/advanced/deposit/observer.ts @@ -19,20 +19,21 @@ async function checkIsDopositClosed(spenderAddress: Address, funderAddress: Addr abi: abiLock, data: transaction.input, }); - const functionNamePlusArgs = `${parsedMethod.functionName}(${parsedMethod.args.join(",")})`; - console.log(chalk.magenta("\ncall:", functionNamePlusArgs)); + const functionNameWithArgs = `${parsedMethod.functionName}(${parsedMethod.args.join(",")})`; + console.log(chalk.magenta("\nContract transaction log:")); + console.log(chalk.magenta("call:"), functionNameWithArgs); console.log(chalk.magenta("event:"), log["eventName"]); console.log(chalk.magenta("from:"), transaction.from); console.log(chalk.magenta("hash:"), transaction.hash, "\n"); const functionName = parsedMethod.functionName.toLowerCase(); - // if deposit is closed by spender (requestor), stop observing + // if deposit is closed by spender (requestor) if (functionName.includes("close") && transaction.from === spenderAddress) { - console.log(chalk.grey(`Deposit has been closed by spender.`)); + console.log(chalk.blueBright(`Deposit has been closed by spender.`)); return true; } - // if deposit is terminated by spender (requestor), stop observing + // if deposit is terminated by funder if (functionName === "terminatedeposit" && transaction.from === funderAddress) { - console.log(chalk.grey(`Deposit has been terminated by spender.`)); + console.log(chalk.blueBright(`Deposit has been terminated by spender.`)); return true; } } @@ -44,7 +45,7 @@ async function startWatchingContractTransactions(address: string) { const funderAddress =
config.funder.address; let isDepositClosed = false; - const stopWatchingContractTransactions = publicClient.watchEvent({ + const unwatch = publicClient.watchEvent({ onLogs: async (logs) => (isDepositClosed = await checkIsDopositClosed(spenderAddress, funderAddress, logs)), events: parseAbi([ "event DepositCreated(uint256 indexed id, address spender)", @@ -57,7 +58,7 @@ async function startWatchingContractTransactions(address: string) { address:
config.lockPaymentContract.holeskyAddress, }); return { - stopWatchingContractTransactions, + stopWatchingContractTransactions: unwatch, isDepositClosed: () => isDepositClosed, }; } From 4ee3ef5f2556bc69f9ab62fe21a1acd2fc363875 Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Thu, 5 Sep 2024 11:48:42 +0200 Subject: [PATCH 4/9] chore: added comments to deposit example --- examples/rental-model/advanced/deposit/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rental-model/advanced/deposit/index.ts b/examples/rental-model/advanced/deposit/index.ts index 4fa0fa1dd..31ef312ec 100644 --- a/examples/rental-model/advanced/deposit/index.ts +++ b/examples/rental-model/advanced/deposit/index.ts @@ -1,12 +1,12 @@ /** * In this example we demonstrate executing tasks on a golem but using funds deposited by another person. - * In this example, it is called Funder. The funder is responsible for allocating the deposit, + * It is called Funder. The funder is responsible for allocating the deposit, * which will then be used by the Spender (requestor) to create an allocation for a payment. * * To run the example, it is necessary to define the funder's address in the config.ts file and a private key * that will allow depositing specific funds on the contract. * - * In order to check if everything went correctly, the observer object logs transaction information + * In order to check if everything went correctly, the Observer logs transaction information * in the smart contract and the script waits for confirmation on the blockchain until the deposit is closed. */ From c5de499fda6e952c7b6561dae25d7fd19b0d0186 Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Thu, 5 Sep 2024 12:04:46 +0200 Subject: [PATCH 5/9] chore: added comments to deposit example --- examples/rental-model/advanced/deposit/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/rental-model/advanced/deposit/index.ts b/examples/rental-model/advanced/deposit/index.ts index 31ef312ec..ab1b7dd30 100644 --- a/examples/rental-model/advanced/deposit/index.ts +++ b/examples/rental-model/advanced/deposit/index.ts @@ -8,6 +8,8 @@ * * In order to check if everything went correctly, the Observer logs transaction information * in the smart contract and the script waits for confirmation on the blockchain until the deposit is closed. + * + * IMPORTANT: this feature is only supported with yagna versions >= 0.16.0 */ import funder from "./funder"; From 4ba7e7c3eb8acb04053e9f1ca054502feb749f4f Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Fri, 6 Sep 2024 11:13:19 +0200 Subject: [PATCH 6/9] chore: removed unnecessary deps from main package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 60b4a0cd4..72ca267f1 100644 --- a/package.json +++ b/package.json @@ -133,8 +133,7 @@ "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", "typedoc-theme-hierarchy": "4.1.2", - "typescript": "^5.3.3", - "viem": "^2.21.1" + "typescript": "^5.3.3" }, "optionalDependencies": { "@rollup/rollup-darwin-x64": "^4", From 22239e071521977d1d5fa2b25df3b652032d4d6d Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Fri, 6 Sep 2024 11:19:26 +0200 Subject: [PATCH 7/9] chore: updated package-lock --- package-lock.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52121a89b..d0700ff38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,8 +82,7 @@ "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", "typedoc-theme-hierarchy": "4.1.2", - "typescript": "^5.3.3", - "viem": "^2.21.1" + "typescript": "^5.3.3" }, "engines": { "node": ">=18.0.0" From 3a0869b72a86718a334a4d3f8a2c1490e0cfdf95 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 6 Sep 2024 19:05:33 +0200 Subject: [PATCH 8/9] docs(examples): added whitelist outbound example (#1075) * docs(examples): added whitelist outbound example * docs(examples): updated note on outbound example to point the CLI helper commands * docs(examples): addressed PR remarks --- .../advanced/outbound/whitelist/manifest.json | 32 +++++++ .../whitelist/read-golem-js-releases.ts | 88 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 examples/rental-model/advanced/outbound/whitelist/manifest.json create mode 100644 examples/rental-model/advanced/outbound/whitelist/read-golem-js-releases.ts diff --git a/examples/rental-model/advanced/outbound/whitelist/manifest.json b/examples/rental-model/advanced/outbound/whitelist/manifest.json new file mode 100644 index 000000000..7f2804dd5 --- /dev/null +++ b/examples/rental-model/advanced/outbound/whitelist/manifest.json @@ -0,0 +1,32 @@ +{ + "version": "0.1.0", + "createdAt": "2024-08-21T21:55:37.123+02:00", + "expiresAt": "2024-11-19T21:55:37.123+01:00", + "metadata": { + "name": "outbound-example-project", + "version": "1.0.0" + }, + "payload": [ + { + "platform": { + "os": "linux", + "arch": "x86_64" + }, + "hash": "sha3:f985714e913cf2f448c8dac86b3b4a82d3f2e7ece490e24428d6c675", + "urls": [ + "http://registry.golem.network/download/656b365b59fb63da42918f861a1ddba85c61223021efd4cc9ef0c017089ac0df" + ] + } + ], + "compManifest": { + "version": "0.1.0", + "net": { + "inet": { + "out": { + "urls": ["https://registry.npmjs.org"], + "protocols": ["https"] + } + } + } + } +} diff --git a/examples/rental-model/advanced/outbound/whitelist/read-golem-js-releases.ts b/examples/rental-model/advanced/outbound/whitelist/read-golem-js-releases.ts new file mode 100644 index 000000000..4ad74ac22 --- /dev/null +++ b/examples/rental-model/advanced/outbound/whitelist/read-golem-js-releases.ts @@ -0,0 +1,88 @@ +/** + * Whitelist Outbound Internet Access Example + * + * This example presents how you can leverage the whitelist outbound rule. + * + * Few things to keep in mind: + * + * - your Requestor script has to present a manifest (manifest.json) as part of the demand when negotiating + * with Providers + * - the Providers need to have the URL which you're trying to reach on their whitelist, if the address + * which you're using is not on the pre-installed one (which the providers can clear if they want to) + * then you need to reach out to the Provider community (you can use Golem Network's Discord) and + * request opening some URL by them for you : + * + * @link https://github.com/golemfactory/ya-installer-resources/tree/main/whitelist Pre-installed whitelist + * + * This example reaches out to the whitelisted registry.npmjs.org to download golem-js release information + * + * WORKING WITH THE MANIFEST + * + * `@golem-sdk/cli` to the rescue - check the `manifest` sub-command to generate the manifest for your + * requestor and maintain the outbound configuration + * + */ +import { GolemNetwork } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "url"; + +const dirName = path.dirname(fileURLToPath(import.meta.url)); + +(async () => { + const logger = pinoPrettyLogger({ + level: "info", + }); + + const glm = new GolemNetwork({ + logger, + }); + + try { + await glm.connect(); + + /** + * Used to terminate the script after 60s in any case + * + * It's possible that no provider will be accepting your offer due to not having the base URL + * which you are trying to reach on their whitelist, or they don't allow whitelist outbound + * access at all. + */ + const timeoutSignal = AbortSignal.timeout(120_000); + const onTimeout = () => console.log("Reached timeout, no one wanted to collaborate"); + timeoutSignal.addEventListener("abort", onTimeout); + + const rental = await glm.oneOf({ + order: { + demand: { + workload: { + imageTag: "golem/node:latest", + manifest: fs.readFileSync(path.join(dirName, "manifest.json")).toString("base64"), + }, + }, + market: { + rentHours: 15 / 60, + pricing: { + model: "burn-rate", + avgGlmPerHour: 1, + }, + }, + }, + signalOrTimeout: timeoutSignal, + }); + + timeoutSignal.removeEventListener("abort", onTimeout); + + const exe = await rental.getExeUnit(); + const result = await exe.run("curl https://registry.npmjs.org/-/package/@golem-sdk/golem-js/dist-tags"); + + console.log("golem-js release tags:", result.getOutputAsJson()); + + await rental.stopAndFinalize(); + } catch (err) { + console.error(err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); From 48a8a355dc4b32f67be9c3108b5280ff5fcee2c8 Mon Sep 17 00:00:00 2001 From: Seweryn Kras Date: Sat, 7 Sep 2024 12:45:35 +0200 Subject: [PATCH 9/9] docs(examples): disable implicit `any` in examples --- examples/rental-model/advanced/deposit/observer.ts | 2 +- examples/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rental-model/advanced/deposit/observer.ts b/examples/rental-model/advanced/deposit/observer.ts index 2957f68cc..2af2d3871 100644 --- a/examples/rental-model/advanced/deposit/observer.ts +++ b/examples/rental-model/advanced/deposit/observer.ts @@ -22,7 +22,7 @@ async function checkIsDopositClosed(spenderAddress: Address, funderAddress: Addr const functionNameWithArgs = `${parsedMethod.functionName}(${parsedMethod.args.join(",")})`; console.log(chalk.magenta("\nContract transaction log:")); console.log(chalk.magenta("call:"), functionNameWithArgs); - console.log(chalk.magenta("event:"), log["eventName"]); + console.log(chalk.magenta("event:"), "eventName" in log ? log["eventName"] : ""); console.log(chalk.magenta("from:"), transaction.from); console.log(chalk.magenta("hash:"), transaction.hash, "\n"); const functionName = parsedMethod.functionName.toLowerCase(); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index b09c90abe..019ecf870 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -3,7 +3,7 @@ "module": "esnext", "target": "esnext", "strict": true, - "noImplicitAny": false, + "noImplicitAny": true, "esModuleInterop": true, "moduleResolution": "Bundler", "removeComments": true,