UNPKG

shield-bridge-sdk

Version:
889 lines (888 loc) 45.7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { spawn, Thread, Worker } from 'threads'; import { OpKind, } from '@taquito/taquito'; import { defaults, tokensGetTokens } from '@tzkt/sdk-api'; import BigNumber from 'bignumber.js'; export const tzktApiMap = { mainnet: 'https://api.tzkt.io', ghostnet: 'https://api.ghostnet.tzkt.io', }; export const saplingStateMapContract = { mainnet: 'KT1WorWEWjfQqQ1X2BFQiCc4hE3DuDKQVH4U', ghostnet: 'KT1WorWEWjfQqQ1X2BFQiCc4hE3DuDKQVH4U', }; var OperationIndex; (function (OperationIndex) { OperationIndex[OperationIndex["UPDATE_OPERATORS_ADD_INDEX"] = 0] = "UPDATE_OPERATORS_ADD_INDEX"; OperationIndex[OperationIndex["APPROVE_INDEX"] = 1] = "APPROVE_INDEX"; OperationIndex[OperationIndex["DEFAULT_INDEX"] = 2] = "DEFAULT_INDEX"; OperationIndex[OperationIndex["UPDATE_OPERATORS_REMOVE_INDEX"] = 3] = "UPDATE_OPERATORS_REMOVE_INDEX"; })(OperationIndex || (OperationIndex = {})); const MINIMAL_FEE_MUTEZ = 100; const MINIMAL_FEE_PER_BYTE_MUTEZ = 1; const MINIMAL_FEE_PER_GAS_MUTEZ = 0.1; const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; // Default to loading the unbundled worker let workerUrl = './worker'; if (isBrowser) { // Load the worker bundle in the browser environment workerUrl = new URL('./workerBundle.js', import.meta.url).href; } /** * ShieldBridgeSDK provides an abstraction to interact with the Shield Bridge smart contract * to shield, unshield, and transfer sapling tokens. * @class * @param {ShieldBridgeSDKConfig} config The configuration object for the Shield Bridge SDK * @param {TezosToolkit} config.client The TezosToolkit instance * @param {'mainnet' | 'ghostnet'} [config.tzktApi='mainnet'] The tzkt API to use * @param {number} [config.minConfirmations=1] The minimum number of confirmations for the transaction * @param {string} [config.saplingStateMapContract='KT1WorWEWjfQqQ1X2BFQiCc4hE3DuDKQVH4U'] The sapling state map contract address * @param {number} [config.gasLimitBuffer=2_000] The buffer to add to the estimated gas limit * @param {number} [config.storageLimitBuffer=500] The buffer to add to the estimated storage limit * @param {boolean} [config.useBaseUnits=false] Whether to use base unit for the token amounts (mutez or token units with decimals) * @param {number} [config.parallelThreads=false] Whether to spawn parallel threads for the sapling worker * @param {string} [config.saplingSecret] The sapling secret key * @param {string} [config.saplingMnemonic] The sapling mnemonic * @returns {ShieldBridgeSDK} The Shield Bridge SDK instance * @example * const tezos = new TezosToolkit('https://mainnet.api.tez.ie'); * const signerProvider = await InMemorySigner.fromSecretKey('edsk...'); * tezos.setSignerProvider(signerProvider); * const shieldBridge = new ShieldBridgeSDK({ * client: tezos, * saplingSecret: 'sask...' * }); * await shieldBridge.shield([ * { * amount: 1, * contract: 'KT1...', * tokenId: 0, * memo: 'abcdefgh' * } * ]); */ export class ShieldBridgeSDK { constructor(config) { var _a, _b, _c, _d, _e, _f; this.config = config; this.initializeSaplingWorker = () => __awaiter(this, void 0, void 0, function* () { // Wait for the worker to be ready yield new Promise((resolve) => { setTimeout(resolve, 2000); }); try { this.saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); return true; } catch (err) { console.log(err.message); throw new Error('Failed to initialize Sapling worker'); } }); /** * @description Get the sapling id for the token contract and token id if provided * @param {string} [contract] The token contract address * @param {number} [tokenId] The token id * @returns The sapling id for the token contract and token id if provided */ this.getSaplingId = (contract, tokenId) => __awaiter(this, void 0, void 0, function* () { var _a; const contractStorage = yield fetch(`${defaults.baseUrl}/v1/contracts/${this.saplingStateMapContract}/storage`).then((res) => res.json()); if (contract) { if (tokenId !== undefined) { return (_a = contractStorage.token_fa_2.find((token) => token.key.address === contract && token.key.nat === `${tokenId}`)) === null || _a === void 0 ? void 0 : _a.value; } return contractStorage.token_fa_1_2[contract]; } return contractStorage.tez; }); /** * @description Get the metadata for the token contract and token id if provided * @param {string} contract The token contract address * @param {number} [tokenId] The token id * @returns The metadata for the token contract and token id if provided */ // eslint-disable-next-line class-methods-use-this this.getTokenMetadata = (contract, tokenId) => __awaiter(this, void 0, void 0, function* () { const [metadata] = yield tokensGetTokens(Object.assign({ contract: { eq: contract, }, select: { fields: ['metadata'], } }, (tokenId ? { tokenId: { eq: `${tokenId}` } } : {}))); return metadata; }); /** * @description Get the number of decimals for the token contract and token id if provided * @param {string} contract The token contract address * @param {number} [tokenId] The token id * @returns The number of decimals for the token contract and token id if provided */ this.getTokenDecimals = (contract, tokenId) => __awaiter(this, void 0, void 0, function* () { const { decimals } = (yield this.getTokenMetadata(contract, tokenId)); return parseInt(decimals, 10); }); /** * @description Estimate the gas and storage limits for the transaction list of shielding transactions * @param {OrderedTransactionList} transactionList The constructed transaction list * @returns The estimated gas and storage limits for the transaction list */ this.estimateShieldTransactionLimits = (transactionList) => __awaiter(this, void 0, void 0, function* () { const contractEstimator = yield this.tezosClient.contract.at(this.saplingStateMapContract); const batch = []; for (let index = 0; index < transactionList.length; index += 1) { /** * These transactions are not yet formatted for the contract call. The default * index transactions can be submitted in a single call with a list of transactions. * This is being done to optimize the number of operations in the transaction. */ if (index === OperationIndex.DEFAULT_INDEX) { const nonTezTransactions = []; // eslint-disable-next-line no-restricted-syntax for (const transaction of transactionList[index]) { const { amount } = transaction, rest = __rest(transaction, ["amount"]); // amount is only present for tez deposits if (!amount) { nonTezTransactions.push(rest); // eslint-disable-next-line no-continue continue; } batch.push([ contractEstimator.methodsObject.default([rest]), { amount, mutez: true, }, ]); } // If token deposits are present, batch them separately from tez deposits if (nonTezTransactions.length) { batch.push([ contractEstimator.methodsObject.default(nonTezTransactions), {}, ]); } } else { // These transactions are already formatted to be included in the batch call transactionList[index].forEach((transaction) => { batch.push([transaction, {}]); }); } } const estimateBatch = batch.map(([operation, params = {}]) => (Object.assign({ kind: OpKind.TRANSACTION }, operation.toTransferParams(params)))); return this.tezosClient.estimate.batch(estimateBatch); }); /** * @description Get the estimated fee for the transaction * @param {Estimate} estimate The estimate object * @returns The estimated fee for the transaction */ this.getEstimatedFee = (estimate) => { const operationFeeMutez = (estimate.gasLimit + this.gasLimitBuffer) * MINIMAL_FEE_PER_GAS_MUTEZ + Number(estimate.opSize) * MINIMAL_FEE_PER_BYTE_MUTEZ; return Math.ceil(Number(operationFeeMutez + MINIMAL_FEE_MUTEZ * 1.2)); }; /** * @description Submit sapling deposits/shielding transactions * @param {SaplingDeposits} saplingDeposits Sapling deposits/shielding transactions to be submitted * @param {number} saplingDeposits.amount The amount to be shielded * @param {string[]} saplingDeposits.saplingTransactions The sapling transactions to be submitted * @param {string} [saplingDeposits.contract] The token contract address * @param {number} [saplingDeposits.tokenId] The token id * @param {string} [saplingDeposits.owner] The shielded address to apply the shielded tokens * @returns The confirmation of the submitted sapling deposits/shielding transactions */ this.submitSaplingShieldTransaction = (saplingDeposits) => __awaiter(this, void 0, void 0, function* () { const dappContract = yield this.tezosClient.wallet.at(this.saplingStateMapContract); const transactionList = [[], [], [], []]; // eslint-disable-next-line no-restricted-syntax for (const saplingDeposit of saplingDeposits) { const { owner, amount, saplingTransactions, contract, tokenId } = saplingDeposit; if (contract) { // eslint-disable-next-line no-await-in-loop const tokenContract = yield this.tezosClient.wallet.at(contract); if (tokenId !== undefined) { // FA2 update_operators add_operator transactionList[OperationIndex.UPDATE_OPERATORS_ADD_INDEX].push(tokenContract.methodsObject.update_operators([ { add_operator: { owner, operator: this.saplingStateMapContract, token_id: tokenId, }, }, ])); // Sapling State Contract default transactionList[OperationIndex.DEFAULT_INDEX].push({ txns: saplingTransactions, contract, token_id: tokenId, }); // FA2 update_operators remove_operator transactionList[OperationIndex.UPDATE_OPERATORS_REMOVE_INDEX].push(tokenContract.methodsObject.update_operators([ { remove_operator: { owner, operator: this.saplingStateMapContract, token_id: tokenId, }, }, ])); } else { // FA1.2 approve transactionList[OperationIndex.APPROVE_INDEX].push(tokenContract.methodsObject.approve({ value: amount, spender: this.saplingStateMapContract, })); // Sapling State Contract default transactionList[OperationIndex.DEFAULT_INDEX].push({ txns: saplingTransactions, contract, }); } } else { // Tez transaction transactionList[OperationIndex.DEFAULT_INDEX].push({ txns: saplingTransactions, amount, }); } } const estimates = yield this.estimateShieldTransactionLimits(transactionList); const batch = this.tezosClient.wallet.batch(); for (let index = 0; index < transactionList.length; index += 1) { /** * These transactions are not yet formatted for the contract call. The default * index transactions can be submitted in a single call with a list of transactions. * This is being done to optimize the number of operations in the transaction. */ if (index === OperationIndex.DEFAULT_INDEX) { const nonTezTransactions = []; // eslint-disable-next-line no-restricted-syntax for (const transaction of transactionList[index]) { const { amount } = transaction, rest = __rest(transaction, ["amount"]); // amount is only present for tez deposits if (!amount) { nonTezTransactions.push(rest); // eslint-disable-next-line no-continue continue; } const estimate = estimates.shift(); batch.withContractCall(dappContract.methodsObject.default([rest]), { // @ts-ignore string is an acceptible type for amount amount, mutez: true, gasLimit: estimate.gasLimit + this.gasLimitBuffer, storageLimit: estimate.storageLimit + this.storageLimitBuffer, fee: this.getEstimatedFee(estimate), }); } // If token deposits are present, batch them separately from tez deposits if (nonTezTransactions.length) { const estimate = estimates.shift(); batch.withContractCall(dappContract.methodsObject.default(nonTezTransactions), { gasLimit: estimate.gasLimit + this.gasLimitBuffer, storageLimit: estimate.storageLimit + this.storageLimitBuffer, fee: this.getEstimatedFee(estimate), }); } } else { // These transactions are already formatted to be included in the batch call transactionList[index].forEach((transaction) => { estimates.shift(); batch.withContractCall(transaction); }); } } return batch.send().then((op) => op.confirmation(this.minConfirmations)); }); /** * @description Submit sapling withdrawals/unshielding transactions * @param {SaplingTransactions} saplingWithdrawals Sapling withdrawals/unshielding transactions to be submitted * @param {string[]} saplingWithdrawals.saplingTransactions The sapling transactions to be submitted * @param {string} [saplingWithdrawals.contract] The token contract address * @param {number} [saplingWithdrawals.tokenId] The token id * @returns The confirmation of the submitted sapling withdrawals/unshielding transactions */ this.submitSaplingUnshieldTransaction = (saplingWithdrawals) => __awaiter(this, void 0, void 0, function* () { const dappContract = yield this.tezosClient.wallet.at(this.saplingStateMapContract); const dappContractEstimator = yield this.tezosClient.contract.at(this.saplingStateMapContract); const saplingWithdrawalMethodObject = saplingWithdrawals.map((saplingWithdrawal) => ({ txns: saplingWithdrawal.saplingTransactions, contract: saplingWithdrawal.contract, token_id: saplingWithdrawal.tokenId, })); const operation = dappContractEstimator.methodsObject.default(saplingWithdrawalMethodObject); const estimate = yield this.tezosClient.estimate.contractCall(operation); return dappContract.methodsObject .default(saplingWithdrawalMethodObject) .send({ gasLimit: estimate.gasLimit + this.gasLimitBuffer, storageLimit: estimate.storageLimit + this.storageLimitBuffer, fee: this.getEstimatedFee(estimate), }) .then((op) => op.confirmation(this.minConfirmations)); }); /** * @description Submit sapling transfers transactions * @param {SaplingTransactions} saplingTransfers Sapling transfers to be submitted * @param {string[]} saplingTransfers.saplingTransactions The sapling transactions to be submitted * @param {string} [saplingTransfers.contract] The token contract address * @param {number} [saplingTransfers.tokenId] The token id * @returns The confirmation of the submitted sapling transfers */ this.submitSaplingTransferTransaction = (saplingTransfers) => __awaiter(this, void 0, void 0, function* () { const dappContract = yield this.tezosClient.wallet.at(this.saplingStateMapContract); const dappContractEstimator = yield this.tezosClient.contract.at(this.saplingStateMapContract); const saplingTransferMethodObject = saplingTransfers.map((saplingTransfer) => ({ txns: saplingTransfer.saplingTransactions, contract: saplingTransfer.contract, token_id: saplingTransfer.tokenId, })); const operation = dappContractEstimator.methodsObject.default(saplingTransferMethodObject); const estimate = yield this.tezosClient.estimate.contractCall(operation); return dappContract.methodsObject .default(saplingTransferMethodObject) .send({ gasLimit: estimate.gasLimit + this.gasLimitBuffer, storageLimit: estimate.storageLimit + this.storageLimitBuffer, fee: this.getEstimatedFee(estimate), }) .then((op) => op.confirmation(this.minConfirmations)); }); /** * @description Construct the sapling parameters for the shielded transaction * @param shieldParam The sapling shielding parameters * @param {number} shieldParam.amount The amount to be shielded * @param {string} [shieldParam.shieldedAddress] The shielded address to apply the shielded tokens * @param {string} [shieldParam.contract] The token contract address * @param {number} [shieldParam.tokenId] The token id * @param {string} [shieldParam.memo] The memo to be included in the sapling transaction * @returns The sapling parameters for the shielded transaction */ this.constructShieldTokenParams = (shieldParam) => __awaiter(this, void 0, void 0, function* () { const { amount, shieldedAddress, contract, tokenId, memo } = shieldParam; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } const saplingId = yield this.getSaplingId(contract, tokenId); if (!saplingId) { throw new Error('Sapling state not initialized for the token'); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingId}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); // Default token decimals let tokenDecimals = 6; if (contract) { tokenDecimals = yield this.getTokenDecimals(contract, tokenId); } let unitAmount = amount; if (!this.useBaseUnits) { unitAmount = new BigNumber(10) .exponentiatedBy(tokenDecimals) .times(amount) .toString(); } let to = shieldedAddress; // If no shielded address is provided, default to the loaded sapling payment address if (!to) { const saplingPaymentAddress = yield saplingWorker.getPaymentAddress(); to = saplingPaymentAddress.address; } const saplingTxn = yield saplingWorker.prepareShieldedTransaction([ { to, // @ts-ignore string is an acceptible type for amount amount: unitAmount, memo, mutez: true, }, ]); if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } const owner = yield this.tezosClient.wallet.pkh(); return { saplingTransactions: [saplingTxn], owner, amount: unitAmount, contract, tokenId, }; }); /** * @description Shield the specified amount of unshielded tokens to the sapling address * @param {ShieldParams} shieldParams Sapling shielding parameters to be constructed into sapling transactions * @param {number} shieldParams.amount The amount to be shielded * @param {string} [shieldParams.shieldedAddress] The shielded address to apply the shielded tokens * @param {string} [shieldParams.contract] The token contract address * @param {number} [shieldParams.tokenId] The token id * @param {string} [shieldParams.memo] The memo to be included in the sapling transaction * @returns The confirmation of the submitted sapling shielding transactions */ this.shield = (shieldParams) => __awaiter(this, void 0, void 0, function* () { let contractParams = []; if (this.parallelThreads) { const shieldParamPromises = shieldParams.map((shieldParam) => this.constructShieldTokenParams(shieldParam)); contractParams = yield Promise.all(shieldParamPromises); } else { for (let i = 0; i < shieldParams.length; i += 1) { const shieldParam = shieldParams[i]; const contractParam = // eslint-disable-next-line no-await-in-loop yield this.constructShieldTokenParams(shieldParam); contractParams.push(contractParam); } } return this.submitSaplingShieldTransaction(contractParams); }); /** * @description Construct the sapling parameters for the unshielded transaction * @param unshieldParam The sapling unshielding parameters * @param {number} unshieldParam.amount The amount to be unshielded * @param {string} [unshieldParam.unshieldedAddress] The unshielded address to apply the unshielded tokens * @param {string} [unshieldParam.contract] The token contract address * @param {number} [unshieldParam.tokenId] The token id * @returns The sapling parameters for the unshielded transaction */ this.constructUnshieldTokenParams = (unshieldParam) => __awaiter(this, void 0, void 0, function* () { const { amount, unshieldedAddress, contract, tokenId } = unshieldParam; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } const saplingId = yield this.getSaplingId(contract, tokenId); if (!saplingId) { throw new Error('Sapling state not initialized for the token'); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingId}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); // Default token decimals let tokenDecimals = 6; if (contract) { tokenDecimals = yield this.getTokenDecimals(contract, tokenId); } let unitAmount = amount; if (!this.useBaseUnits) { unitAmount = new BigNumber(10) .exponentiatedBy(tokenDecimals) .times(amount) .toString(); } let to = unshieldedAddress; // If no unshielded address is provided, default to the wallet public key hash if (!to) { to = yield this.tezosClient.wallet.pkh(); } const saplingTxn = yield saplingWorker.prepareUnshieldedTransaction({ to, // @ts-ignore string is an acceptible type for amount amount: unitAmount, mutez: true, }); if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } return { saplingTransactions: [saplingTxn], contract, tokenId, }; }); /** * @description Unshield the specified amount of shielded tokens from the sapling address * @param {UnshieldParams} unshieldParams Sapling unshielding parameters to be constructed into sapling transactions * @param {number} unshieldParams.amount The amount to be unshielded * @param {string} [unshieldParams.unshieldedAddress] The unshielded address to apply the unshielded tokens * @param {string} [unshieldParams.contract] The token contract address * @param {number} [unshieldParams.tokenId] The token id * @returns The confirmation of the submitted sapling unshielding transactions */ this.unshield = (unshieldParams) => __awaiter(this, void 0, void 0, function* () { let contractParams = []; if (this.parallelThreads) { const unshieldParamPromises = unshieldParams.map((unshieldParam) => this.constructUnshieldTokenParams(unshieldParam)); contractParams = yield Promise.all(unshieldParamPromises); } else { for (let i = 0; i < unshieldParams.length; i += 1) { const unshieldParam = unshieldParams[i]; const contractParam = // eslint-disable-next-line no-await-in-loop yield this.constructUnshieldTokenParams(unshieldParam); contractParams.push(contractParam); } } return this.submitSaplingUnshieldTransaction(contractParams); }); /** * @description Construct the sapling parameters for the transfer transaction * @param transferParam The sapling transfer parameters * @param {string} [transferParam.contract] The token contract address * @param {number} [transferParam.tokenId] The token id * @param {object} transferParam.transfers The transfers to be made * @returns The sapling parameters for the transfer transaction */ this.constructTransferTokenParams = (transferParam) => __awaiter(this, void 0, void 0, function* () { const { contract, tokenId, transfers } = transferParam; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } const saplingId = yield this.getSaplingId(contract, tokenId); if (!saplingId) { throw new Error('Sapling state not initialized for the token'); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingId}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); // Default token decimals let tokenDecimals = 6; if (contract) { tokenDecimals = yield this.getTokenDecimals(contract, tokenId); } const saplingTransfers = transfers.map(({ amount, to, memo }) => { let unitAmount = amount; if (!this.useBaseUnits) { unitAmount = new BigNumber(10) .exponentiatedBy(tokenDecimals) .times(amount) .toString(); } return { to, amount: unitAmount, memo, mutez: true, }; }); const saplingTxn = // @ts-ignore string is an acceptible type for amount yield saplingWorker.prepareSaplingTransaction(saplingTransfers); if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } return { saplingTransactions: [saplingTxn], contract, tokenId, }; }); /** * @description Transfer the specified amount of shielded tokens to the specified shielded address * @param {TransferParams[]} transferParams Sapling transfer parameters to be constructed into sapling transactions * @param {string} [transferParams.contract] The token contract address * @param {number} [transferParams.tokenId] The token id * @param {object} transferParams.transfers The transfers to be made * @returns The confirmation of the submitted sapling transfer transactions */ this.transfer = (transferParams) => __awaiter(this, void 0, void 0, function* () { let contractParams = []; if (this.parallelThreads) { const unshieldParamPromises = transferParams.map((transferParam) => this.constructTransferTokenParams(transferParam)); contractParams = yield Promise.all(unshieldParamPromises); } else { for (let i = 0; i < transferParams.length; i += 1) { const transferParam = transferParams[i]; const contractParam = // eslint-disable-next-line no-await-in-loop yield this.constructTransferTokenParams(transferParam); contractParams.push(contractParam); } } return this.submitSaplingTransferTransaction(contractParams); }); /** * @description Get the shielded sapling token balance for the currently loaded shielded address * @param {SaplingTokenInfo} saplingTokenInfo The sapling token information * @param {number} [saplingTokenInfo.saplingId] The sapling id * @param {string} [saplingTokenInfo.contract] The token contract address * @param {number} [saplingTokenInfo.tokenId] The token id * @returns The shielded sapling token balance for the currently loaded shielded address */ this.getShieldedBalance = (_a) => __awaiter(this, [_a], void 0, function* ({ saplingId, contract, tokenId, }) { let saplingIdQuery = saplingId; if (!saplingIdQuery) { saplingIdQuery = yield this.getSaplingId(contract, tokenId); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingIdQuery}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); const balance = (yield saplingWorker.getSaplingBalance()); let tokenDecimals = 6; if (contract) { tokenDecimals = yield this.getTokenDecimals(contract, tokenId); } if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } if (this.useBaseUnits) { return balance; } return new BigNumber(balance) .dividedBy(new BigNumber(10).exponentiatedBy(tokenDecimals)) .toNumber(); }); /** * @description Get all the shielded sapling tokens * @param includeMetadata Include the metadata for the shielded sapling tokens * @returns The shielded sapling tokens */ this.getAllShieldedAssets = (...args_1) => __awaiter(this, [...args_1], void 0, function* (includeMetadata = false) { const contractStorage = yield fetch(`${tzktApiMap.ghostnet}/v1/contracts/${this.saplingStateMapContract}/storage`).then((res) => res.json()); const saplingIds = [ { saplingId: contractStorage.tez, }, ]; contractStorage.token_fa_2.forEach(({ key, value }) => { saplingIds.push({ saplingId: value, contract: key.address, tokenId: parseInt(key.nat, 10), }); }); Object.entries(contractStorage.token_fa_1_2).forEach(([contract, saplingId]) => { saplingIds.push({ saplingId, contract }); }); if (!includeMetadata) { return saplingIds; } const withMetadata = saplingIds.map((saplingId) => { if (saplingId.contract) { return this.getTokenMetadata(saplingId.contract, saplingId.tokenId).then((tokenMetadata) => (Object.assign(Object.assign({}, saplingId), { metadata: tokenMetadata }))); } return saplingId; }); return Promise.all(withMetadata); }); /** * @description Get the shielded sapling token balances for all the sapling tokens * @returns The shielded sapling token balances for all the sapling tokens */ this.getAllShieldedBalances = () => __awaiter(this, void 0, void 0, function* () { const saplingIds = yield this.getAllShieldedAssets(); const balances = []; for (let i = 0; i < saplingIds.length; i += 1) { const saplingToken = saplingIds[i]; // eslint-disable-next-line no-await-in-loop const balance = yield this.getShieldedBalance(saplingToken); balances.push(Object.assign(Object.assign({}, saplingToken), { balance })); } return balances; }); /** * @description Get the shielded incoming and outgoing transactions for the specified sapling contract and token id * @param {string} [contract] Sapling contract address * @param {number} [tokenId] Token id * @returns The shielded incoming and outgoing transactions for the specified sapling contract and token id */ this.getShieldedTransactions = (contract, tokenId) => __awaiter(this, void 0, void 0, function* () { const saplingId = yield this.getSaplingId(contract, tokenId); if (!saplingId) { throw new Error('Sapling state not initialized for the token'); } let tokenDecimals = 6; if (contract) { tokenDecimals = yield this.getTokenDecimals(contract, tokenId); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingId}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); const transactions = yield saplingWorker.getSaplingTransactions(); if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } return { incoming: transactions.incoming.map((transaction) => { if (this.useBaseUnits) { return transaction; } const value = new BigNumber(transaction.value) .dividedBy(new BigNumber(10).exponentiatedBy(tokenDecimals)) .toNumber(); return Object.assign(Object.assign({}, transaction), { value }); }), outgoing: transactions.outgoing.map((transaction) => { if (this.useBaseUnits) { return transaction; } const value = new BigNumber(transaction.value) .dividedBy(new BigNumber(10).exponentiatedBy(tokenDecimals)) .toNumber(); return Object.assign(Object.assign({}, transaction), { value }); }), }; }); /** * @description Get the sapling payment address of the currently loaded sapling key * @returns The sapling payment address */ this.getShieldedAddress = () => __awaiter(this, void 0, void 0, function* () { const saplingId = yield this.getSaplingId(); if (!saplingId) { throw new Error('Sapling state not initialized for the token'); } const skType = this.config.saplingSecret ? 'secretKey' : 'mnemonic'; yield this.ready; // eslint-disable-next-line prefer-destructuring let saplingWorker = this.saplingWorker; if (this.parallelThreads) { saplingWorker = yield spawn(new Worker(workerUrl), { timeout: 120000, }); } yield saplingWorker.loadSaplingSecret({ sk: skType === 'secretKey' ? this.config.saplingSecret : this.config.saplingMnemonic, skType, saplingDetails: { contractAddress: this.saplingStateMapContract, memoSize: 8, saplingId: `${saplingId}`, }, rpcUrl: this.tezosClient.rpc.getRpcUrl(), }); const saplingPaymentAddress = yield saplingWorker.getPaymentAddress(); if (this.parallelThreads) { yield Thread.terminate(saplingWorker); } return saplingPaymentAddress.address; }); /** * @description Initialize the sapling pool for the specified token contract and token id * @param {string} contract The token contract address * @param {number} [tokenId] The token id * @returns The confirmation of the initialized sapling pool */ this.initTokenSaplingPool = (contract, tokenId) => __awaiter(this, void 0, void 0, function* () { const dappContract = yield this.tezosClient.wallet.at(this.saplingStateMapContract); return dappContract.methodsObject .init_token_sapling_pool({ contract, token_id: tokenId, }) .send() .then((op) => op.confirmation(this.minConfirmations)); }); this.tezosClient = config.client; this.minConfirmations = (_a = config.minConfirmations) !== null && _a !== void 0 ? _a : 1; this.saplingStateMapContract = (_b = config.saplingStateMapContract) !== null && _b !== void 0 ? _b : saplingStateMapContract.mainnet; this.gasLimitBuffer = (_c = config.gasLimitBuffer) !== null && _c !== void 0 ? _c : 2000; this.storageLimitBuffer = (_d = config.storageLimitBuffer) !== null && _d !== void 0 ? _d : 500; this.useBaseUnits = (_e = config.useBaseUnits) !== null && _e !== void 0 ? _e : false; this.parallelThreads = (_f = config.parallelThreads) !== null && _f !== void 0 ? _f : false; // This prevents multiple instances with a separate baseUrl since the SDK is a singleton defaults.baseUrl = tzktApiMap[this.config.tzktApi || 'mainnet']; this.ready = this.initializeSaplingWorker(); } }