myria-core-sdk
Version:
Latest version SDK
856 lines • 80.8 kB
JavaScript
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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