import algosdk, { Account, Algodv2, assignGroupID, makePaymentTxnWithSuggestedParamsFromObject, Transaction, waitForConfirmation, } from "@certusone/wormhole-sdk/node_modules/algosdk"; import { getForeignAssetAlgorand } from "@certusone/wormhole-sdk/lib/cjs/token_bridge"; import { ChainId } from "@certusone/wormhole-sdk/lib/cjs/utils"; import { TransactionSignerPair, _parseVAAAlgorand, } from "@certusone/wormhole-sdk/lib/cjs/algorand"; const ci = !!process.env.CI; const ALGO_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const ALGOD_ADDRESS: string = ci ? "http://algorand" : "http://localhost"; const ALGOD_PORT: number = 4001; /** * Creates a new Algodv2 client using local file consts * @returns a newly constructed Algodv2 client */ export function getAlgoClient(): Algodv2 { const algodClient = new Algodv2(ALGO_TOKEN, ALGOD_ADDRESS, ALGOD_PORT); return algodClient; } let KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const KMD_ADDRESS: string = ci ? "http://algorand" : "http://localhost"; const KMD_PORT: number = 4002; const KMD_WALLET_NAME: string = "unencrypted-default-wallet"; const KMD_WALLET_PASSWORD: string = ""; export function getKmdClient(): algosdk.Kmd { const kmdClient: algosdk.Kmd = new algosdk.Kmd( KMD_TOKEN, KMD_ADDRESS, KMD_PORT ); return kmdClient; } export async function getGenesisAccounts(): Promise { let retval: Account[] = []; const kmd: algosdk.Kmd = getKmdClient(); // Get list of wallets const wallets = (await kmd.listWallets()).wallets; if (!wallets) { console.error("No wallets found!"); return retval; } // Walk walles to find correct wallet let myWalletId: string = ""; wallets.forEach((element: any) => { // console.log(element); if (element.name === KMD_WALLET_NAME) { myWalletId = element.id; } }); if (myWalletId.length === 0) { console.error("invalid wallet ID"); return retval; } // Get the wallet handle for Genesis wallet const myWalletHandle = ( await kmd.initWalletHandle(myWalletId, KMD_WALLET_PASSWORD) ).wallet_handle_token; // console.log("myWalletHandle:", myWalletHandle); // Get the 3 addresses associated with the Genesis wallet const addresses = (await kmd.listKeys(myWalletHandle)).addresses; // console.log("addresses:", addresses); for (let i = 0; i < addresses.length; i++) { const element = addresses[i]; const myExportedKey: Buffer = ( await kmd.exportKey(myWalletHandle, KMD_WALLET_PASSWORD, element) ).private_key; // console.log("exported key:", element, myExportedKey); let mn = algosdk.secretKeyToMnemonic(myExportedKey); let ta = algosdk.mnemonicToSecretKey(mn); retval.push(ta); } kmd.releaseWalletHandle(myWalletHandle); // console.log("length of genesis accounts:", retval.length); return retval; } export async function firstKmdTransaction() { try { const genAccounts = await getGenesisAccounts(); // const walletRsp = await myKmdClient.getWallet(myWalletHandle); // console.log("walletRsp:", walletRsp); } catch (e) { console.log("KMD transaction error:", e); } } // This function creates temporary accounts and funds them with the // Genesis accounts. export async function getTempAccounts(): Promise { let retval: Account[] = []; const algodClient = getAlgoClient(); const genesisAccounts: Account[] = await getGenesisAccounts(); const numAccts = genesisAccounts.length; if (numAccts === 0) { console.error("Failed to get genesisAccounts"); return retval; } // console.log("About to construct txns..."); const params = await algodClient.getTransactionParams().do(); let transactions: Transaction[] = []; for (let i = 0; i < numAccts; i++) { let newAcct = createAccount(); if (!newAcct) { throw new Error("failed to create a temp account"); } let fundingAcct = genesisAccounts[i]; // Create a payment transaction // console.log( // "Creating paytxn with fundAcct", // fundingAcct, // "newAcct", // newAcct // ); const payTxn = makePaymentTxnWithSuggestedParamsFromObject({ from: fundingAcct.addr, to: newAcct.addr, amount: 15000000, suggestedParams: params, }); // Sign the transaction // console.log("signing paytxn..."); const signedTxn = payTxn.signTxn(fundingAcct.sk); const signedTxnId = payTxn.txID().toString(); // console.log("signedTxnId:", signedTxnId); // Submit the transaction // console.log("submitting transaction..."); const txId = await algodClient.sendRawTransaction(signedTxn).do(); // console.log("submitted txId:", txId); // Wait for response const confirmedTxn = await algosdk.waitForConfirmation( algodClient, signedTxnId, 4 ); //Get the completed Transaction // console.log( // "Transaction " + // txId + // " confirmed in round " + // confirmedTxn["confirmed-round"] // ); // console.log("Confirmation response:", confirmedTxn); // let mytxinfo = JSON.stringify(confirmedTxn.txn.txn, undefined, 2); // console.log("Transaction information: %o", mytxinfo); // let string = new TextDecoder().decode(confirmedTxn.txn.txn.note); // console.log("Note field: ", string); let accountInfo = await algodClient.accountInformation(newAcct.addr).do(); // console.log( // "Transaction Amount: %d microAlgos", // confirmedTxn.txn.txn.amt // ); // console.log("Transaction Fee: %d microAlgos", confirmedTxn.txn.txn.fee); // console.log("Account balance: %d microAlgos", accountInfo.amount); retval.push(newAcct); } return retval; } export function createAccount(): Account | undefined { try { const retval = algosdk.generateAccount(); // let retval = new Account(tempAcct.addr, Buffer.from(tempAcct.sk)); // let account_mnemonic = algosdk.secretKeyToMnemonic(tempAcct.sk); // console.log("Account Address = " + retval.addr); // console.log("Account Mnemonic = " + account_mnemonic); // console.log("Class Account:", retval); return retval; } catch (err) { console.log("err", err); } } /** * Return the balances of all assets for the supplied account * @param client An Algodv2 client * @param account The account containing assets * @returns Map of asset index to qty */ export async function getBalances( client: Algodv2, account: string ): Promise> { let balances = new Map(); const accountInfo = await client.accountInformation(account).do(); console.log("Account Info:", accountInfo); console.log("Account Info|created-assets:", accountInfo["created-assets"]); // Put the algo balance in key 0 balances.set(0, accountInfo.amount); const assets: Array = accountInfo.assets; console.log("assets", assets); assets.forEach(function (asset) { console.log("inside foreach", asset); const assetId = asset["asset-id"]; const amount = asset.amount; balances.set(assetId, amount); }); console.log("balances", balances); return balances; } /** * Return the balance of the supplied asset index for the supplied account * @param client An Algodv2 client * @param account The account to query for the supplied asset index * @param assetId The asset index * @returns The quantity of the asset in the supplied account */ export async function getBalance( client: Algodv2, account: string, assetId: bigint ): Promise { const accountInfo = await client.accountInformation(account).do(); if (assetId === BigInt(0)) { return accountInfo.amount; } let ret = BigInt(0); const assets: Array = accountInfo.assets; assets.forEach((asset) => { if (Number(assetId) === asset["asset-id"]) { ret = asset.amount; return; } }); return ret; } export async function createAsset(account: Account): Promise { console.log("Creating asset..."); const aClient = getAlgoClient(); const params = await aClient.getTransactionParams().do(); const note = undefined; // arbitrary data to be stored in the transaction; here, none is stored // Asset creation specific parameters const addr = account.addr; // Whether user accounts will need to be unfrozen before transacting const defaultFrozen = false; // integer number of decimals for asset unit calculation const decimals = 10; // total number of this asset available for circulation const totalIssuance = 1000000; // Used to display asset units to user const unitName = "NORIUM"; // Friendly name of the asset const assetName = "ChuckNorium"; // Optional string pointing to a URL relating to the asset // const assetURL = "http://www.chucknorris.com"; const assetURL = ""; // Optional hash commitment of some sort relating to the asset. 32 character length. // const assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d"; const assetMetadataHash = ""; // The following parameters are the only ones // that can be changed, and they have to be changed // by the current manager // Specified address can change reserve, freeze, clawback, and manager const manager = account.addr; // Specified address is considered the asset reserve // (it has no special privileges, this is only informational) const reserve = account.addr; // Specified address can freeze or unfreeze user asset holdings const freeze = account.addr; // Specified address can revoke user asset holdings and send // them to other addresses const clawback = account.addr; // signing and sending "txn" allows "addr" to create an asset const txn = algosdk.makeAssetCreateTxnWithSuggestedParams( addr, note, totalIssuance, decimals, defaultFrozen, manager, reserve, freeze, clawback, unitName, assetName, assetURL, assetMetadataHash, params ); const rawSignedTxn = txn.signTxn(account.sk); // console.log("rawSignedTxn:", rawSignedTxn); const tx = await aClient.sendRawTransaction(rawSignedTxn).do(); // wait for transaction to be confirmed const ptx = await algosdk.waitForConfirmation(aClient, tx.txId, 4); // console.log("createAsset() - ptx:", ptx); // Get the new asset's information from the creator account const assetID: number = ptx["asset-index"]; //Get the completed Transaction console.log( "createAsset() - Transaction " + tx.txId + " confirmed in round " + ptx["confirmed-round"] ); return assetID; } export async function createNFT(account: Account): Promise { console.log("Creating NFT..."); const aClient = getAlgoClient(); const params = await aClient.getTransactionParams().do(); // Asset creation specific parameters const addr = account.addr; // Whether user accounts will need to be unfrozen before transacting const defaultFrozen = false; // integer number of decimals for asset unit calculation const decimals = 0; // total number of this asset available for circulation const total = 1; // Used to display asset units to user const unitName = "CNART"; // Friendly name of the asset const assetName = "ChuckNoriumArtwork@arc3"; // Optional string pointing to a URL relating to the asset const assetURL = "http://www.chucknorris.com"; // Optional hash commitment of some sort relating to the asset. 32 character length. const assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d"; // The following parameters are the only ones // that can be changed, and they have to be changed // by the current manager // Specified address can change reserve, freeze, clawback, and manager const manager = account.addr; // Specified address is considered the asset reserve // (it has no special privileges, this is only informational) const reserve = account.addr; // Specified address can freeze or unfreeze user asset holdings const freeze = account.addr; // Specified address can revoke user asset holdings and send // them to other addresses const clawback = account.addr; // signing and sending "txn" allows "addr" to create an asset const txn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({ from: addr, total, decimals, assetName, unitName, assetURL, assetMetadataHash, defaultFrozen, freeze, manager, clawback, reserve, suggestedParams: params, }); const rawSignedTxn = txn.signTxn(account.sk); // console.log("rawSignedTxn:", rawSignedTxn); const tx = await aClient.sendRawTransaction(rawSignedTxn).do(); // wait for transaction to be confirmed const ptx = await algosdk.waitForConfirmation(aClient, tx.txId, 4); // Get the new asset's information from the creator account const assetID: number = ptx["asset-index"]; console.log( "createNFT() - Transaction " + tx.txId + " confirmed in round " + ptx["confirmed-round"] ); return assetID; } export async function getForeignAssetFromVaaAlgorand( client: Algodv2, tokenBridgeId: bigint, vaa: Uint8Array ): Promise { const parsedVAA = _parseVAAAlgorand(vaa); if (parsedVAA.Contract === undefined) { throw "parsedVAA.Contract is undefined"; } return await getForeignAssetAlgorand( client, tokenBridgeId, parsedVAA.FromChain as ChainId, parsedVAA.Contract ); } export async function signSendAndConfirmAlgorand( algodClient: Algodv2, txs: TransactionSignerPair[], wallet: Account ) { assignGroupID(txs.map((tx) => tx.tx)); const signedTxns: Uint8Array[] = []; for (const tx of txs) { if (tx.signer) { signedTxns.push(await tx.signer.signTxn(tx.tx)); } else { signedTxns.push(tx.tx.signTxn(wallet.sk)); } } await algodClient.sendRawTransaction(signedTxns).do(); const result = await waitForConfirmation( algodClient, txs[txs.length - 1].tx.txID(), 1 ); return result; }