+
+
+ ${pendingTransactions.
+ filter((item)=> item.type === 'PAYMENT').map(
+ (payment) => `
+
+
Recipient: ${
+ payment.recipientAddress
+ }
+
Amount: ${payment.amount}
+
`
+ )
+ .join("")}
+ ${[...pendingTransactions, ...pendingAdditionalArbitraryTxs].
+ filter((item)=> item.type === 'ARBITRARY').map(
+ (arbitraryTx) => `
+
+
Service: ${
+ arbitraryTx.service
+ }
+
Name: ${name}
+
Identifier: ${
+ arbitraryTx.identifier
+ }
+
`
+ )
+ .join("")}
+
+
+ `,
+ highlightedText: `Total Amount: ${totalAmount}`,
+ fee: fee
+ },
+ isFromExtension
+ );
+ const { accepted, checkbox1 = false } = resPermission;
+ if (!accepted) {
+ throw new Error("User declined request");
+ }
+
+
+
+
+ // const failedTxs = []
+ const paymentsDone = {
+
+ }
+
+ const transactionsDone = []
+
+
+ for (const transaction of pendingTransactions) {
+ const type = transaction.type;
+
+ if (type === "PAYMENT") {
+ const makePayment = await retryTransaction(
+ transferAsset,
+ [{ amount: transaction.amount, assetId, recipient: transaction.recipientAddress }], true
+ );
+ if (makePayment) {
+ transactionsDone.push(makePayment?.signature);
+ if (transaction.paymentRefId) {
+ paymentsDone[transaction.paymentRefId] = makePayment
+ }
+ }
+ }
+ else if (type === "ARBITRARY" && paymentsDone[transaction.paymentRefId]) {
+ const objectToEncrypt = {
+ data: transaction.base64,
+ payment: paymentsDone[transaction.paymentRefId],
+ };
+
+ const toBase64 = await retryTransaction(objectToBase64, [objectToEncrypt], true);
+
+ if (!toBase64) continue; // Skip if encryption fails
+
+ const encryptDataResponse = await retryTransaction(encryptDataGroup, [
+ {
+ data64: toBase64,
+ publicKeys: transaction.publicKeys,
+ privateKey,
+ userPublicKey,
+ },
+ ], true);
+
+ if (!encryptDataResponse) continue; // Skip if encryption fails
+
+ const resPublish = await retryTransaction(publishData, [
+ {
+ registeredName: encodeURIComponent(name),
+ file: encryptDataResponse,
+ service: transaction.service,
+ identifier: encodeURIComponent(transaction.identifier),
+ uploadType: "file",
+ description: transaction?.description,
+ isBase64: true,
+ apiVersion: 2,
+ withFee: true,
+ },
+ ], true);
+
+ if (resPublish?.signature) {
+ transactionsDone.push(resPublish?.signature);
+ }
+ }
+ }
+
+ for (const transaction of pendingAdditionalArbitraryTxs) {
+
+ const objectToEncrypt = {
+ data: transaction.base64,
+ };
+
+ const toBase64 = await retryTransaction(objectToBase64, [objectToEncrypt], true);
+
+ if (!toBase64) continue; // Skip if encryption fails
+
+ const encryptDataResponse = await retryTransaction(encryptDataGroup, [
+ {
+ data64: toBase64,
+ publicKeys: transaction.publicKeys,
+ privateKey,
+ userPublicKey,
+ },
+ ], true);
+
+ if (!encryptDataResponse) continue; // Skip if encryption fails
+
+ const resPublish = await retryTransaction(publishData, [
+ {
+ registeredName: encodeURIComponent(name),
+ file: encryptDataResponse,
+ service: transaction.service,
+ identifier: encodeURIComponent(transaction.identifier),
+ uploadType: "file",
+ description: transaction?.description,
+ isBase64: true,
+ apiVersion: 2,
+ withFee: true,
+ },
+ ], true);
+
+ if (resPublish?.signature) {
+ transactionsDone.push(resPublish?.signature);
+ }
+
+ }
+
+ return transactionsDone
+};
+
+
+export const transferAssetRequest = async (data, isFromExtension) => {
+ const requiredFields = ["amount", "assetId", "recipient"];
+ requiredFields.forEach((field) => {
+ if (data[field] === undefined || data[field] === null) {
+ throw new Error(`Missing required field: ${field}`);
+ }
+ });
+ const amount = data.amount
+ const assetId = data.assetId
+ const recipient = data.recipient
+
+
+ const {fee} = await getFee("TRANSFER_ASSET");
+ const balance = await getBalanceInfo();
+
+ if(+balance < +fee) throw new Error('Your QORT balance is insufficient')
+ const assetBalance = await getAssetBalanceInfo(assetId)
+ if(assetBalance < amount) throw new Error('Your asset balance is insufficient')
+ const confirmReceiver = await getNameOrAddress(recipient);
+ if (confirmReceiver.error) {
+ throw new Error("Invalid receiver address or name");
+ }
+ const assetInfo = await getAssetInfo(assetId)
+ const resPermission = await getUserPermission(
+ {
+ text1: `Do you give this application permission to transfer the following asset?`,
+ text2: `Asset: ${assetInfo?.name}`,
+ highlightedText: `Amount: ${amount}`,
+ fee: fee
+ },
+ isFromExtension
+ );
+
+ const { accepted } = resPermission;
+ if (!accepted) {
+ throw new Error("User declined request");
+ }
+ const res = await transferAsset({amount, recipient: confirmReceiver, assetId})
+ return res
+}
+
+export const signForeignFees = async (data, isFromExtension) => {
+ const resPermission = await getUserPermission(
+ {
+ text1: `Do you give this application permission to sign the required fees for all your trade offers?`,
+ },
+ isFromExtension
+ );
+ const { accepted } = resPermission;
+ if (accepted) {
+ const wallet = await getSaveWallet();
+ const address = wallet.address0;
+ const resKeyPair = await getKeyPair();
+ const parsedData = resKeyPair;
+ const uint8PrivateKey = Base58.decode(parsedData.privateKey);
+ const uint8PublicKey = Base58.decode(parsedData.publicKey);
+ const keyPair = {
+ privateKey: uint8PrivateKey,
+ publicKey: uint8PublicKey,
+ };
+
+ const unsignedFeesUrl = await createEndpoint(
+ `/crosschain/unsignedfees/${address}`
+ );
+
+ const unsignedFeesResponse = await fetch(unsignedFeesUrl);
+
+ const unsignedFees = await unsignedFeesResponse.json();
+
+ const signedFees = [];
+
+ unsignedFees.forEach((unsignedFee) => {
+ const unsignedDataDecoded = Base58.decode(unsignedFee.data);
+
+ const signature = nacl.sign.detached(
+ unsignedDataDecoded,
+ keyPair.privateKey
+ );
+
+ const signedFee = {
+ timestamp: unsignedFee.timestamp,
+ data: `${Base58.encode(signature)}`,
+ atAddress: unsignedFee.atAddress,
+ fee: unsignedFee.fee,
+ };
+
+ signedFees.push(signedFee);
+ });
+
+ const signedFeesUrl = await createEndpoint(`/crosschain/signedfees`);
+
+ await fetch(signedFeesUrl, {
+ method: 'POST',
+ headers: {
+ Accept: '*/*',
+ 'Content-Type': 'application/json',
+ },
+ body: `${JSON.stringify(signedFees)}`,
+ });
+
+ return true;
+ } else {
+ throw new Error('User declined request');
+ }
};
\ No newline at end of file
diff --git a/src/transactions/TransferAssetTransaction.ts b/src/transactions/TransferAssetTransaction.ts
new file mode 100644
index 0000000..9d0bedb
--- /dev/null
+++ b/src/transactions/TransferAssetTransaction.ts
@@ -0,0 +1,35 @@
+// @ts-nocheck
+
+import { QORT_DECIMALS } from '../constants/constants'
+import TransactionBase from './TransactionBase'
+
+export default class TransferAssetTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 12
+ }
+
+ set recipient(recipient) {
+ this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
+ }
+
+ set amount(amount) {
+ this._amount = Math.round(amount * QORT_DECIMALS)
+ this._amountBytes = this.constructor.utils.int64ToBytes(this._amount)
+ }
+
+ set assetId(assetId) {
+ this._assetId = this.constructor.utils.int64ToBytes(assetId)
+ }
+
+ get params() {
+ const params = super.params
+ params.push(
+ this._recipient,
+ this._assetId,
+ this._amountBytes,
+ this._feeBytes
+ )
+ return params
+ }
+}
diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts
index cb0f9c3..268b631 100644
--- a/src/transactions/transactions.ts
+++ b/src/transactions/transactions.ts
@@ -24,6 +24,7 @@ import UpdateGroupTransaction from './UpdateGroupTransaction.js'
import SellNameTransacion from './SellNameTransacion.js'
import CancelSellNameTransacion from './CancelSellNameTransacion.js'
import BuyNameTransacion from './BuyNameTransacion.js'
+import TransferAssetTransaction from './TransferAssetTransaction.js'
export const transactionTypes = {
3: RegisterNameTransaction,
@@ -34,6 +35,7 @@ export const transactionTypes = {
7: BuyNameTransacion,
8: CreatePollTransaction,
9: VoteOnPollTransaction,
+ 12: TransferAssetTransaction,
16: DeployAtTransaction,
18: ChatTransaction,
181: GroupChatTransaction,
diff --git a/src/utils/decode.ts b/src/utils/decode.ts
index 3123810..06fdb2b 100644
--- a/src/utils/decode.ts
+++ b/src/utils/decode.ts
@@ -13,4 +13,19 @@ export function decodeIfEncoded(input) {
// Return input as-is if not URI-encoded
return input;
- }
\ No newline at end of file
+ }
+
+ export const isValidBase64 = (str: string): boolean => {
+ if (typeof str !== "string" || str.length % 4 !== 0) return false;
+
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
+ return base64Regex.test(str);
+ };
+
+ export const isValidBase64WithDecode = (str: string): boolean => {
+ try {
+ return isValidBase64(str) && Boolean(atob(str));
+ } catch {
+ return false;
+ }
+ };
\ No newline at end of file