UNPKG

myria-core-sdk

Version:

Latest version SDK

856 lines 80.8 kB
import Web3 from "web3"; import { AssetMarketpAPI } from "../core/apis/asset.marketp.api"; import { CommonAPI } from "../core/apis/common.api"; import { TransactionAPI } from "../core/apis/transaction.api"; import { ActionBy, MultiTransactionType, } from "../types/GameTransactionTypes"; import { TokenType, } from "../typesBundle"; import { SignatureTxManager } from "./SignatureTxManager"; import { v4 as uuidv4 } from "uuid"; import { createHash } from "crypto"; import { convertAmountToQuantizedAmount, convertUsdtToQuantizedAmount, DEFAULT_QUANTUM, QUANTUM_USDT, TOKEN_ADDRESS_USDT } from "../utils"; const sigUtil = require("eth-sig-util"); export class GameTransactionManager { constructor(iNativeClient) { this.transactionAPI = new TransactionAPI(iNativeClient.env); this.assetMarketplaceAPI = new AssetMarketpAPI(iNativeClient.env); this.commonAPI = new CommonAPI(iNativeClient.env); this.signatureTxManager = new SignatureTxManager(); } async transferERC20Tokens(params) { if (!params.sourceWalletSignature) { throw new Error('Source wallet signature is required'); } if (!params.sourceWalletAddress) { throw new Error('Source wallet address is required'); } if (!params.destinationWalletAddress) { throw new Error('Destination wallet address is required'); } if (!params.amount) { throw new Error('Transferred amount is required'); } if (!params.tokenAddress) { throw new Error('Token address is required'); } if (!params.groupRequestId) { throw new Error('Group request ID is required'); } if (!params.partnerRefId) { throw new Error('Partner Ref ID is required'); } const isUsdtToken = params.tokenAddress === TOKEN_ADDRESS_USDT; const quantum = params.quantum || DEFAULT_QUANTUM; if (isUsdtToken && quantum !== QUANTUM_USDT) { throw new Error('Need to input correct quantum for USDT token address! Quantum for USDT token address must be 1000000'); } // Get stark private key for transaction execute const senderStarkPrivateKey = await this.signatureTxManager.getPrivateStarkKey(params.sourceWalletSignature); // Get transaction details item const quantizedAmount = isUsdtToken ? convertUsdtToQuantizedAmount(params.amount) : convertAmountToQuantizedAmount(params.amount); const transactionItemDetail = { quantizedAmount: String(quantizedAmount), receiverWalletAddress: params.destinationWalletAddress, tokenType: TokenType.ERC20, tokenData: { tokenAddress: params.tokenAddress, quantum, } }; const signableTransferredERC20Params = { senderWalletAddress: params.sourceWalletAddress, items: [transactionItemDetail], }; const fullPayloadTransferData = await this.transactionAPI.signableBulkTransfer(signableTransferredERC20Params); const fullTransferPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(params.sourceWalletAddress, TokenType.ERC20, fullPayloadTransferData, senderStarkPrivateKey); const bulkTransferRequest = { senderWalletAddress: params.sourceWalletAddress, requestId: params.requestId, groupRequestId: params.groupRequestId, partnerRefId: params.partnerRefId, description: params.description || 'Transfer_ERC20_SDK', items: fullTransferPayload, isWaitingForValidation: true, }; const transferResponse = await this.transactionAPI.bulkTransferERC20Token(bulkTransferRequest); console.log('Transfer ERC20 response => ', JSON.stringify(transferResponse)); return transferResponse; } async transferNfts(params) { if (!params.senderPrivateKey) { throw new Error('Sender private key is required'); } if (!params.sourceWalletAddress) { throw new Error('Source wallet address is required'); } if (!params.destinationWalletAddress) { throw new Error('Destination wallet address is required'); } if (!params.tokenAddress) { throw new Error('Token address is required'); } if (!params.tokenId) { throw new Error('Token ID is required'); } if (!params.groupRequestId) { throw new Error('Group request ID is required'); } if (!params.partnerRefId) { throw new Error('Partner Ref ID is required'); } // Get transaction details item const transactionItemDetail = { quantizedAmount: '1', receiverWalletAddress: params.destinationWalletAddress, tokenType: TokenType.MINTABLE_ERC721, tokenData: { tokenAddress: params.tokenAddress, tokenId: params.tokenId } }; const signableTransferredERC721Params = { senderWalletAddress: params.sourceWalletAddress, items: [transactionItemDetail], }; const fullPayloadTransferData = await this.transactionAPI.signableBulkTransfer(signableTransferredERC721Params); const fullTransferPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(params.sourceWalletAddress, TokenType.MINTABLE_ERC721, fullPayloadTransferData, params.senderPrivateKey); const bulkTransferRequest = { senderWalletAddress: params.sourceWalletAddress, requestId: params.requestId, groupRequestId: params.groupRequestId, partnerRefId: params.partnerRefId, description: params.description || 'Transfer_NFT_SDK', items: fullTransferPayload, isWaitingForValidation: true, }; const bulkTransferResult = await this.assetMarketplaceAPI.bulkTransferERC721Token(bulkTransferRequest); console.log('Transfer NFTs response => ', JSON.stringify(bulkTransferResult)); return bulkTransferResult; } async transferNftsWithSig(params) { if (!params.sourceWalletSignature) { throw new Error('Source wallet signature is required'); } if (!params.sourceWalletAddress) { throw new Error('Source wallet address is required'); } if (!params.destinationWalletAddress) { throw new Error('Destination wallet address is required'); } if (!params.tokenAddress) { throw new Error('Token address is required'); } if (!params.tokenId) { throw new Error('Token ID is required'); } if (!params.groupRequestId) { throw new Error('Group request ID is required'); } if (!params.partnerRefId) { throw new Error('Partner Ref ID is required'); } // Get transaction details item const transactionItemDetail = { quantizedAmount: '1', receiverWalletAddress: params.destinationWalletAddress, tokenType: TokenType.MINTABLE_ERC721, tokenData: { tokenAddress: params.tokenAddress, tokenId: params.tokenId } }; const signableTransferredERC721Params = { senderWalletAddress: params.sourceWalletAddress, items: [transactionItemDetail], }; const fullPayloadTransferData = await this.transactionAPI.signableBulkTransfer(signableTransferredERC721Params); const senderL2PrivateKey = await this.signatureTxManager.getPrivateStarkKey(params.sourceWalletSignature); if (!senderL2PrivateKey) { throw new Error(`Sender L2 Private key is failed to generate with signature ${params.sourceWalletSignature}`); } const fullTransferPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(params.sourceWalletAddress, TokenType.MINTABLE_ERC721, fullPayloadTransferData, senderL2PrivateKey); const bulkTransferRequest = { senderWalletAddress: params.sourceWalletAddress, requestId: params.requestId, groupRequestId: params.groupRequestId, partnerRefId: params.partnerRefId, description: params.description || 'Transfer_NFT_SDK', items: fullTransferPayload, isWaitingForValidation: true, }; const bulkTransferResult = await this.assetMarketplaceAPI.bulkTransferERC721Token(bulkTransferRequest); console.log('Transfer NFTs response => ', JSON.stringify(bulkTransferResult)); return bulkTransferResult; } async burnNfts(params) { if (!params.sourceWalletSignature) { throw new Error('Sender private key is required'); } if (!params.sourceWalletAddress) { throw new Error('Source wallet address is required'); } params.burnTokens.forEach(token => { if (!token.tokenAddress) { throw new Error('Explicit token info is required - Token address is required'); } if (!token.tokenId) { throw new Error('Explicit token info is required - Token address is required'); } }); if (!params.groupRequestId) { throw new Error('Group request ID is required'); } if (!params.partnerRefId) { throw new Error('Partner Ref ID is required'); } // Get transaction details item const burnTokensSignable = []; for (const burnToken of params.burnTokens) { if (!burnToken.tokenAddress) { continue; } const burnTokenData = { quantizedAmount: '1', tokenType: TokenType.MINTABLE_ERC721, tokenData: { tokenAddress: burnToken.tokenAddress || '', tokenId: burnToken.tokenId } }; burnTokensSignable.push(burnTokenData); } const signableBurnedERC721Params = { senderWalletAddress: params.sourceWalletAddress, items: burnTokensSignable, }; // Get all of signable burn tokens const fullPayloadBurnTokenData = await this.transactionAPI.signableBurnTokens(signableBurnedERC721Params); if (!fullPayloadBurnTokenData) { throw new Error("Burn token data is failed to generate"); } // Generate L2 private key by wallet signature const senderL2PrivateKey = await this.signatureTxManager.getPrivateStarkKey(params.sourceWalletSignature); if (!senderL2PrivateKey) { throw new Error(`Sender L2 Private key is failed to generate with signature ${params.sourceWalletSignature}`); } // Create generateFullPayloadForBurnTransfer list of signature const fullPayloadBurnTransferred = await this.signatureTxManager.generateFullPayloadForBulkTransfer(params.sourceWalletAddress, TokenType.MINTABLE_ERC721, fullPayloadBurnTokenData, senderL2PrivateKey); console.log("Full transfer payload -> ", fullPayloadBurnTransferred); // Trigger transfer request const burnRequestApi = { requestId: params.requestId, groupRequestId: params.groupRequestId, partnerRefId: params.partnerRefId, description: params.description || 'Burn token in SDK', items: fullPayloadBurnTransferred, }; const burnResult = await this.assetMarketplaceAPI.burnNfts(burnRequestApi); console.log("Burn Tokens Response Nft API -> ", JSON.stringify(burnResult)); return burnResult; } async getTransactionDetailsByID(txId) { return this.transactionAPI.getTransactionDetails(txId); } async getTransactionsByPartnerRefID(partnerRefID) { if (!partnerRefID) { throw new Error("Partner reference ID"); } return this.transactionAPI.getTransactionsByPartnerRefID(partnerRefID); } async getTransactionsByGroupRequestIDAndPartnerRefID(groupReqID, partnerRefID, transactionPaging) { if (!partnerRefID) { throw new Error("Partner Reference ID is required"); } if (!groupReqID) { throw new Error("Group Request ID is required"); } return this.transactionAPI.getTransactionsByGroupReqIDAndPartnerRefID(groupReqID, partnerRefID, transactionPaging); } buildBurnTransactionMessage(burnedAssets) { let messageWithTokens = ""; burnedAssets.data.map((item, index) => { if (index === burnedAssets.data.length - 1) { messageWithTokens += `${[item.tokenAddress, item.tokenId]}`; } else { messageWithTokens += `${[item.tokenAddress, item.tokenId]}, `; } }); const transactionMessage = "Please confirm that you acknowledge this burn transaction with below info: " + messageWithTokens.toString(); console.log('Transaction message => ', transactionMessage); return transactionMessage; } buildHashMessageForMultiTx(encodedTxData) { const jsonString = JSON.stringify(encodedTxData); const hash = createHash("sha256"); hash.update(jsonString); const hashTxMsg = hash.digest("hex"); const fullTxMsg = `Please confirm that you acknowledge this transaction: ${hashTxMsg}`; console.log("Hash: ", hashTxMsg); console.log("fullTxMsg: ", fullTxMsg); return fullTxMsg; } _buildBurnSignablePayload(payload) { const burnedItems = []; payload.data.forEach((item) => { if (item.tokenType !== (TokenType.ERC721 && TokenType.MINTABLE_ERC721)) { throw new Error("Only MINTABLE_ERC-721 tokens are valid for this type of burn transfer"); } if (!item.tokenAddress) { throw new Error("Token address is required"); } if (!item.tokenId) { throw new Error("Token ID is required for transfer ERC-721 tokens "); } const transferredItem = { quantizedAmount: "1", tokenType: item.tokenType, tokenData: { tokenAddress: item.tokenAddress, tokenId: item.tokenId, }, }; burnedItems.push(transferredItem); }); return burnedItems; } _buildTransferSignablePayload(payload, receiverWalletAddress) { const transferredItems = []; payload.data.forEach((item) => { if (item.tokenType !== (TokenType.ERC721 && TokenType.MINTABLE_ERC721)) { throw new Error("Only MINTABLE_ERC-721 tokens are valid for this type of burn transfer"); } if (!item.tokenAddress) { throw new Error("Token address is required"); } if (!item.tokenId) { throw new Error("Token ID is required for transfer ERC-721 tokens "); } const transferredItem = { quantizedAmount: "1", receiverWalletAddress, tokenType: item.tokenType, tokenData: { tokenAddress: item.tokenAddress, tokenId: item.tokenId, }, }; transferredItems.push(transferredItem); }); return transferredItems; } async swapAssets(params) { var _a; if (!params.userBurnSignature) { throw new Error("User burn signature is required to execute this operation"); } // validate this signature if (!params.userWalletSignature) { throw new Error("User wallet signature is required"); } if (!params.ownerBurn) { throw new Error("Need to define the BurnBy for authorizing & build signature process"); } if (!params.ownerTransfer) { throw new Error("Need to define the TransferBy for authorizing & build signature process"); } if (!params.requestId) { throw new Error("Request ID is required"); } if (!params.burnAssets) { throw new Error("Burn assets is required"); } // if (!params.transferAssets && !params.mintAssets) { // throw new Error("At least transfer or mint assets are required"); // } // Validate user burn signature const transactionBurnMessage = this.buildBurnTransactionMessage(params.burnAssets); const recoveredUserWalletAddress = await sigUtil.recoverPersonalSignature({ data: transactionBurnMessage, sig: params.userBurnSignature, }); console.log('Recover wallet address => ', recoveredUserWalletAddress); console.log('transactionBurnMessage => ', transactionBurnMessage); if (String(recoveredUserWalletAddress).toLowerCase() !== params.userWalletAddress.toLowerCase()) { throw new Error(`Burn signature is invalid due to the recover wallet ${recoveredUserWalletAddress} was different to ${params.userWalletAddress} `); } // Get user private stark key by wallet signature from user const userPrivateStarkKey = this.signatureTxManager.getPrivateStarkKey(params.userWalletSignature); const burnedItems = this._buildBurnSignablePayload(params.burnAssets); let transferredItems = []; let transferItemsPayload = { senderWalletAddress: "", items: [], }; let transferSignableData = { senderPublicKey: "", items: [], }; if (params.transferAssets && ((_a = params.transferAssets) === null || _a === void 0 ? void 0 : _a.data.length) > 0) { transferredItems = this._buildTransferSignablePayload(params.transferAssets, params.userWalletAddress); transferItemsPayload = { senderWalletAddress: params.ownerTransfer == ActionBy.EndUser ? params.userWalletAddress : params.developerWalletAddress, items: transferredItems, }; transferSignableData = await this.transactionAPI.signableBulkTransfer(transferItemsPayload); } const burnItemsPayload = { senderWalletAddress: params.ownerBurn == ActionBy.EndUser ? params.userWalletAddress : params.developerWalletAddress, items: burnedItems, }; const burnSignableData = await this.transactionAPI.signableBurnTokens(burnItemsPayload); // Build full multi payload let burnMultiRequest = []; if (burnSignableData.items.length > 0) { const senderPrivateStarkKey = params.ownerBurn == ActionBy.EndUser ? userPrivateStarkKey : params.developerPrivateStarkKey; const senderWalletAddress = params.ownerBurn == ActionBy.EndUser ? params.userWalletAddress : params.developerWalletAddress; const burnMultiPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(senderWalletAddress, TokenType.MINTABLE_ERC721, burnSignableData, senderPrivateStarkKey); burnMultiRequest = this._transformSingleToMultiTxPayload(burnMultiPayload, params.partnerRefId, `BURN_BY_${params.ownerBurn}`); } // Build full transfer payload let transferMultiRequest = []; if (transferSignableData.items.length > 0) { const senderPrivateStarkKey = params.ownerTransfer == ActionBy.EndUser ? userPrivateStarkKey : params.developerPrivateStarkKey; const senderWalletAddress = params.ownerTransfer == ActionBy.EndUser ? params.userWalletAddress : params.developerWalletAddress; const transferMultiPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(senderWalletAddress, TokenType.MINTABLE_ERC721, transferSignableData, senderPrivateStarkKey); transferMultiRequest = this._transformSingleToMultiTxPayload(transferMultiPayload, params.partnerRefId, `TRANSFER_BY_${params.ownerTransfer}`); } const multiTransactionPayload = { requestId: params.requestId, developerApiKey: params.developerApiKey, partnerRefId: params.partnerRefId, requestDescription: "Multi_transaction_test_", groupRequestId: params.groupRequestId, burns: burnMultiRequest, transfers: transferMultiRequest.length > 0 ? transferMultiRequest : undefined, mints: params.mintAssets || undefined, }; const swapAssetsResponse = await this.assetMarketplaceAPI.swapAssets(multiTransactionPayload); return swapAssetsResponse; } _transformSingleToMultiTxPayload(payload, partnerRefId, reqDescription) { const payloadWrapper = payload.map((item) => { const randomRequestId = uuidv4(); const itemWrapper = { requestId: randomRequestId, partnerRefId: partnerRefId, requestDescription: reqDescription, ...item, }; return itemWrapper; }); return payloadWrapper; } async _transformSignableBurnToRequestData(payload, partnerRefId, reqDescription) { const bundleTransferRequestAPI = []; for (let i = 0; i < payload.length; i++) { const data = payload[i]; // Generate the signature const signableBurnData = { senderPublicKey: data.senderPublicKey, items: data.items, }; const burnMultiPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(data.senderWalletAddress, TokenType.MINTABLE_ERC721, signableBurnData, data.senderPrivateStarkKey); bundleTransferRequestAPI.push(...burnMultiPayload); } const wrapUpBurnRequestData = this._transformSingleToMultiTxPayload(bundleTransferRequestAPI, partnerRefId, reqDescription); return wrapUpBurnRequestData; } async _transformSignableTransferToRequestData(payload, partnerRefId, reqDescription) { const bundleTransferRequestAPI = []; for (let i = 0; i < payload.length; i++) { const data = payload[i]; // Generate the signature const siganbleTransferData = { senderPublicKey: data.senderPublicKey, items: data.items, }; const transferMultiPayload = await this.signatureTxManager.generateFullPayloadForBulkTransfer(data.senderWalletAddress, TokenType.MINTABLE_ERC721, siganbleTransferData, data.senderPrivateStarkKey); bundleTransferRequestAPI.push(...transferMultiPayload); } const wrapUpTransferRequestData = this._transformSingleToMultiTxPayload(bundleTransferRequestAPI, partnerRefId, reqDescription); return wrapUpTransferRequestData; } serializeTxItem(txData) { const encodedTx = { senderWallet: txData.sourceWallet, receiverWallet: txData.destinationWallet, transactionType: txData.transactionType, tokenInfo: JSON.stringify(txData.data), }; return JSON.stringify(encodedTx); } async _validateTransactions(data) { for (let i = 0; i < data.length; i++) { const item = data[i]; // validate TRANSFER if (item.transactionType === MultiTransactionType.TRANSFER) { if (!item.sourceWallet) { throw new Error("Source wallet is required"); } if (!Web3.utils.isAddress(item.sourceWallet)) { throw new Error("Source wallet must be valid Eth address"); } if (!item.sourceWalletSignature) { throw new Error("Source wallet signature is required"); } if (!item.sourceWalletSignature.startsWith("0x") || item.sourceWalletSignature.length !== 132) { throw new Error("Source wallet signature must be correct format with 0x and contains 132 chars"); } if (!item.sourceWalletSignatureTx) { throw new Error("Source wallet signature for transaction is required"); } if (!item.sourceWalletSignatureTx.startsWith("0x") || item.sourceWalletSignatureTx.length !== 132) { throw new Error("Source wallet signature must be correct format with 0x and contains 132 chars"); } if (!item.destinationWallet) { throw new Error("Destination wallet is required"); } if (!Web3.utils.isAddress(item.destinationWallet)) { throw new Error("Destination wallet must be valid Eth address"); } if (item.destinationWallet === item.sourceWallet) { throw new Error("Source & destination wallet must be different to each of other"); } // Validate source wallet signature const encodeTxMsg = this.serializeTxItem(item); const txMessageHash = this.buildHashMessageForMultiTx(encodeTxMsg); const recoveredSenderWalletAddress = await sigUtil.recoverPersonalSignature({ data: txMessageHash, sig: item.sourceWalletSignatureTx, }); if (String(recoveredSenderWalletAddress).toLowerCase() !== item.sourceWallet.toLowerCase()) { throw new Error(`Source wallet signature is invalid for tx ${item.sourceWallet}`); } } // validate BURN if (item.transactionType === MultiTransactionType.BURN) { if (!item.sourceWallet) { throw new Error("Source wallet is required"); } if (!Web3.utils.isAddress(item.sourceWallet)) { throw new Error("Source wallet must be valid Eth address"); } if (!item.sourceWalletSignature) { throw new Error("Source wallet signature is required"); } if (!item.sourceWalletSignature.startsWith("0x") || item.sourceWalletSignature.length !== 132) { throw new Error("Source wallet signature must be correct format with 0x and contains 132 chars"); } if (item.destinationWallet === item.sourceWallet) { throw new Error("Source & destination wallet must be different to each of other"); } // Validate source wallet signature const encodeTxMsg = this.serializeTxItem(item); const txMessageHash = this.buildHashMessageForMultiTx(encodeTxMsg); const recoveredSenderWalletAddress = await sigUtil.recoverPersonalSignature({ data: txMessageHash, sig: item.sourceWalletSignatureTx, }); if (String(recoveredSenderWalletAddress).toLowerCase() !== item.sourceWallet.toLowerCase()) { throw new Error(`Source wallet signature is invalid for tx ${item.sourceWallet}`); } } // validate MINT_FOR if (item.transactionType === MultiTransactionType.MINT_FOR) { const mintForItem = item; if (!item.sourceWallet) { throw new Error("Source wallet is required"); } if (!Web3.utils.isAddress(item.sourceWallet)) { throw new Error("Source wallet must be valid Eth address"); } if (!mintForItem.senderStarkKey) { throw new Error("Sender stark key is required"); } if (mintForItem.senderStarkKey.length !== 65) { throw new Error("Sender stark key is invalid and required format 0x.. with 65 chars"); } if (!mintForItem.receiverStarkKey) { throw new Error("Receiver stark key is required"); } if (mintForItem.receiverStarkKey.length !== 65) { throw new Error("Receiver stark key is invalid and required format 0x.. with 65 chars"); } if (mintForItem.senderStarkKey.toLowerCase() === mintForItem.receiverStarkKey.toLowerCase()) { throw new Error("Sender stark key and receiver stark key must be different"); } if (!item.destinationWallet) { throw new Error("Destinated wallet is required"); } if (!Web3.utils.isAddress(item.destinationWallet)) { throw new Error("Destination wallet must be valid Eth address"); } if (item.sourceWallet === item.destinationWallet) { throw new Error("Source & destination wallet must be different"); } const mintForData = item.data; mintForData.map((mintItem) => { if (!mintItem.tokenId) { throw new Error("Token ID is required"); } if (!mintItem.tokenAddress) { throw new Error("Token Address is required"); } }); } // validate MINT if (item.transactionType === MultiTransactionType.MINT) { const mintItem = item; if (!item.sourceWallet) { throw new Error("Source wallet is required"); } if (!Web3.utils.isAddress(item.sourceWallet)) { throw new Error("Source wallet must be valid Eth address"); } if (!mintItem.senderStarkKey) { throw new Error("Sender stark key is required"); } if (mintItem.senderStarkKey.length !== 65) { throw new Error("Sender stark key is invalid and required format 0x.. with 65 chars"); } const mintData = item.data; mintData.map((mintItemData) => { if (!mintItemData.tokenId) { throw new Error("Token ID is required"); } if (!mintItemData.tokenAddress) { throw new Error("Token Address is required"); } }); } } return true; } async _transformTransferData(data, partnerRefId) { const itemSignableTransferredMulti = []; for (let i = 0; i < data.length; i++) { const item = data[i]; if (!item.sourceWalletSignature) { throw new Error("Source wallet signature is empty, cant continue the transfer operation !"); } if (!item.sourceWallet) { throw new Error("Source wallet is empty, cant continue the transfer operation !"); } if (!item.destinationWallet) { throw new Error("Destination wallet is required"); } // Retrieve private stark key from signature const sourceWalletPrivStarkKey = this.signatureTxManager.getPrivateStarkKey(item.sourceWalletSignature); const transferPayload = { data: item.data, }; // Build full payload to request signable infomations const signableTransferData = this._buildTransferSignablePayload(transferPayload, item.destinationWallet); // Build request API to signable-bulk-transfer-details const signableItemsPayload = { senderWalletAddress: item.sourceWallet, items: signableTransferData, }; // Call signable-burn endpoints to get the list vaults from sender and multiple receiver const transferredSignableData = await this.transactionAPI.signableBulkTransfer(signableItemsPayload); // Wrap data with some more extra metadata info const itemSignableBurn = { senderPublicKey: transferredSignableData.senderPublicKey, senderPrivateStarkKey: sourceWalletPrivStarkKey, senderWalletAddress: item.sourceWallet, requestDescription: `BURN_BY_${item.sourceWallet}`, partnerRefId, items: transferredSignableData.items, }; itemSignableTransferredMulti.push(itemSignableBurn); } return itemSignableTransferredMulti; } async _transformBurnData(data, partnerRefId) { const itemSignableBurnsMulti = []; for (let i = 0; i < data.length; i++) { const item = data[i]; if (!item.sourceWalletSignature) { throw new Error("Source wallet signature is empty, cant continue the burn operation !"); } if (!item.sourceWallet) { throw new Error("Source wallet is empty, cant continue the burn operation !"); } // Retrieve private stark key from signature const sourceWalletPrivStarkKey = this.signatureTxManager.getPrivateStarkKey(item.sourceWalletSignature); const transferPayload = { data: item.data, }; // Build full payload to request signable infomations const signableTransferData = this._buildBurnSignablePayload(transferPayload); // Build request API to signable-bulk-burn-details const signableItemsPayload = { senderWalletAddress: item.sourceWallet, items: signableTransferData, }; // Call signable-burn endpoints to get the list vaults from sender and multiple receiver const burnedSignableData = await this.transactionAPI.signableBurnTokens(signableItemsPayload); // Wrap data with some more extra metadata info const itemSignableBurn = { senderPublicKey: burnedSignableData.senderPublicKey, senderPrivateStarkKey: sourceWalletPrivStarkKey, senderWalletAddress: item.sourceWallet, requestDescription: `BURN_BY_${item.sourceWallet}`, partnerRefId, items: burnedSignableData.items, }; itemSignableBurnsMulti.push(itemSignableBurn); } return itemSignableBurnsMulti; } _transformMintData(params, partnerRefId) { const mintRequestAPI = []; params.map((item) => { const randomReqId = uuidv4(); item.data.map((tokenItem) => { if (!tokenItem.tokenId) { throw new Error("TokenID is required"); } if (!tokenItem.tokenAddress) { throw new Error("Token address is required"); } const mintData = { requestId: tokenItem.requestId, partnerRefId: partnerRefId || "", requestDescription: `Mint_req_${randomReqId}`, starkKey: item.senderStarkKey, contractAddress: tokenItem.tokenAddress, tokenId: tokenItem.tokenId, description: tokenItem.description, }; mintRequestAPI.push(mintData); }); }); return mintRequestAPI; } _transformMintForData(params, partnerRefId) { const mintRequestAPI = []; params.map((item) => { if (!item.receiverStarkKey) { throw new Error("Required the receiver stark key for MintFor operation"); } if (item.receiverStarkKey === item.senderStarkKey) { throw new Error("Required both of sender and receiver stark key to be different"); } const randomReqId = uuidv4(); item.data.map((tokenItem) => { if (!tokenItem.tokenId) { throw new Error("TokenID is required"); } if (!tokenItem.tokenAddress) { throw new Error("Token address is required"); } const mintData = { requestId: tokenItem.requestId, partnerRefId: partnerRefId || "", requestDescription: `Mint_req_${randomReqId}`, starkKey: item.senderStarkKey, mintForStarkKey: item.receiverStarkKey, contractAddress: tokenItem.tokenAddress, tokenId: tokenItem.tokenId, description: tokenItem.description, }; mintRequestAPI.push(mintData); }); }); return mintRequestAPI; } async executeBundleTransactions(params) { // Validate bundle transactions if (!params.requestId) { throw new Error("Request ID is required"); } if (!params.groupRequestId) { throw new Error("Group Request ID is required"); } if (!params.partnerRefId) { throw new Error("Partner Ref ID is required"); } await this._validateTransactions(params.data); // Categorize transaction list into transfer / burned / minted const transferData = params.data.filter((txItem) => txItem.transactionType === MultiTransactionType.TRANSFER); const burnData = params.data.filter((txItem) => txItem.transactionType === MultiTransactionType.BURN); const mintData = params.data.filter((txItem) => txItem.transactionType === MultiTransactionType.MINT); const mintForData = params.data.filter((txItem) => txItem.transactionType === MultiTransactionType.MINT_FOR); if (transferData.length === 0 && burnData.length === 0 && mintData.length == 0 && mintForData.length === 0) { throw new Error("Operation required at least 1 transactions to execute !"); } // Transform transfer data with signature and build payload API let fullTransferDataRequestAPI = []; if (transferData && transferData.length > 0) { const fullTransferDataWithVault = await this._transformTransferData(transferData, params.partnerRefId); console.log("fullTransferDataWithVault => ", fullTransferDataWithVault); fullTransferDataRequestAPI = await this._transformSignableTransferToRequestData(fullTransferDataWithVault, params.partnerRefId, "Test"); console.log("fullTransferDataRequestAPI => ", fullTransferDataRequestAPI); } // Transform burn data with signature and build payload API let fullBurnDataRequestAPI = []; if (burnData && burnData.length > 0) { const fullBurnDataWithVault = await this._transformBurnData(burnData, params.partnerRefId); console.log("fullBurnDataWithVault => ", fullBurnDataWithVault); fullBurnDataRequestAPI = await this._transformSignableBurnToRequestData(fullBurnDataWithVault, params.partnerRefId, "test"); console.log("fullBurnDataRequestAPI => ", fullBurnDataRequestAPI); } // Transform mint & mintFor data let fullMintDataRequestAPI = []; let fullMintForDataRequestAPI = []; if (mintData.length > 0) { fullMintDataRequestAPI = this._transformMintData(mintData, params.partnerRefId); } if (mintForData.length > 0) { fullMintForDataRequestAPI = this._transformMintForData(mintForData, params.partnerRefId); } let fullMintRequestAPI = undefined; if (fullMintDataRequestAPI.length > 0 && fullMintForDataRequestAPI.length > 0) { fullMintRequestAPI = [ ...fullMintDataRequestAPI, ...fullMintForDataRequestAPI, ]; } else if (fullMintDataRequestAPI.length > 0) { fullMintRequestAPI = [...fullMintDataRequestAPI]; } else if (fullMintForDataRequestAPI.length > 0) { fullMintRequestAPI = [...fullMintForDataRequestAPI]; } const multiTransactionPayload = { requestId: params.requestId, developerApiKey: params.developerApiKey, partnerRefId: params.partnerRefId, requestDescription: "Multi_transaction_test_", groupRequestId: params.groupRequestId, burns: fullBurnDataRequestAPI.length > 0 ? fullBurnDataRequestAPI : undefined, transfers: fullTransferDataRequestAPI.length > 0 ? fullTransferDataRequestAPI : undefined, mints: fullMintRequestAPI, }; const swapAssetsResponse = await this.assetMarketplaceAPI.swapAssets(multiTransactionPayload); return swapAssetsResponse; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2FtZVRyYW5zYWN0aW9uTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL0dhbWVUcmFuc2FjdGlvbk1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRXhCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNqRSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTlELE9BQU8sRUFDTCxRQUFRLEVBU1Isb0JBQW9CLEdBU3JCLE1BQU0sK0JBQStCLENBQUM7QUFDdkMsT0FBTyxFQWFMLFNBQVMsR0FLVixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzFELE9BQU8sRUFBRSxFQUFFLElBQUksTUFBTSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDcEMsT0FBTyxFQUFFLDhCQUE4QixFQUFFLDRCQUE0QixFQUFFLGVBQWUsRUFBRSxZQUFZLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFFM0ksTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBRXhDLE1BQU0sT0FBTyxzQkFBc0I7SUFNakMsWUFBWSxhQUFpQztRQUMzQyxJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksY0FBYyxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUM1RCxJQUFJLENBQUMsbUJBQW1CLEdBQUcsSUFBSSxlQUFlLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2xFLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxTQUFTLENBQUMsYUFBYSxD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