mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-27 05:17:51 +00:00
added asset qrs
This commit is contained in:
parent
2e55c16f41
commit
8b1ad92606
@ -3253,6 +3253,8 @@ function App() {
|
||||
lineHeight: 1.2,
|
||||
maxWidth: "90%",
|
||||
textAlign: "center",
|
||||
fontSize: '16px',
|
||||
marginBottom: '10px'
|
||||
}}
|
||||
>
|
||||
{messageQortalRequestExtension?.text1}
|
||||
|
@ -952,6 +952,28 @@ export async function getBalanceInfo() {
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getAssetBalanceInfo(assetId: number) {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
const validApi = await getBaseApi();
|
||||
const response = await fetch(validApi + `/assets/balances?address=${address}&assetid=${assetId}&ordering=ASSET_BALANCE_ACCOUNT&limit=1`);
|
||||
|
||||
if (!response?.ok) throw new Error("Cannot fetch asset balance");
|
||||
const data = await response.json();
|
||||
return +data?.[0]?.balance
|
||||
}
|
||||
|
||||
export async function getAssetInfo(assetId: number) {
|
||||
const validApi = await getBaseApi();
|
||||
const response = await fetch(validApi + `/assets/info?assetId=${assetId}`);
|
||||
|
||||
if (!response?.ok) throw new Error("Cannot fetch asset info");
|
||||
const data = await response.json();
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
export async function getLTCBalance() {
|
||||
const wallet = await getSaveWallet();
|
||||
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
|
||||
@ -2268,6 +2290,39 @@ export async function kickFromGroup({
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function transferAsset({
|
||||
amount,
|
||||
recipient,
|
||||
assetId,
|
||||
}) {
|
||||
const lastReference = await getLastRef();
|
||||
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 feeres = await getFee("TRANSFER_ASSET");
|
||||
|
||||
const tx = await createTransaction(12, keyPair, {
|
||||
fee: feeres.fee,
|
||||
recipient: recipient,
|
||||
amount: amount,
|
||||
assetId: assetId,
|
||||
lastReference: lastReference,
|
||||
});
|
||||
|
||||
|
||||
const signedBytes = Base58.encode(tx.signedBytes);
|
||||
|
||||
const res = await processTransactionVersion2(signedBytes);
|
||||
if (!res?.signature)
|
||||
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function createGroup({
|
||||
groupName,
|
||||
groupDescription,
|
||||
|
@ -259,7 +259,9 @@ export const listOfAllQortalRequests = [
|
||||
'UPDATE_GROUP',
|
||||
'SELL_NAME',
|
||||
'CANCEL_SELL_NAME',
|
||||
'BUY_NAME'
|
||||
'BUY_NAME',
|
||||
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||
'TRANSFER_ASSET'
|
||||
]
|
||||
|
||||
export const UIQortalRequests = [
|
||||
@ -319,7 +321,9 @@ export const UIQortalRequests = [
|
||||
'UPDATE_GROUP',
|
||||
'SELL_NAME',
|
||||
'CANCEL_SELL_NAME',
|
||||
'BUY_NAME'
|
||||
'BUY_NAME',
|
||||
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||
'TRANSFER_ASSET'
|
||||
];
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { gateways, getApiKeyFromStorage } from "./background";
|
||||
import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener";
|
||||
import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest, buyNameRequest, sellNameRequest, cancelSellNameRequest } from "./qortalRequests/get";
|
||||
import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest, buyNameRequest, sellNameRequest, cancelSellNameRequest, multiPaymentWithPrivateData, transferAssetRequest } from "./qortalRequests/get";
|
||||
import { getData, storeData } from "./utils/chromeStorage";
|
||||
import { executeEvent } from "./utils/events";
|
||||
|
||||
@ -1314,6 +1314,45 @@ export const isRunningGateway = async ()=> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA" : {
|
||||
try {
|
||||
const res = await multiPaymentWithPrivateData(request.payload, isFromExtension)
|
||||
event.source.postMessage({
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
payload: res,
|
||||
type: "backgroundMessageResponse",
|
||||
}, event.origin);
|
||||
} catch (error) {
|
||||
event.source.postMessage({
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
error: error?.message,
|
||||
type: "backgroundMessageResponse",
|
||||
}, event.origin);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "TRANSFER_ASSET" : {
|
||||
try {
|
||||
const res = await transferAssetRequest(request.payload, isFromExtension)
|
||||
event.source.postMessage({
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
payload: res,
|
||||
type: "backgroundMessageResponse",
|
||||
}, event.origin);
|
||||
} catch (error) {
|
||||
event.source.postMessage({
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
error: error?.message,
|
||||
type: "backgroundMessageResponse",
|
||||
}, event.origin);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -35,6 +35,11 @@ import {
|
||||
cancelSellName,
|
||||
buyName,
|
||||
getBaseApi,
|
||||
getAssetBalanceInfo,
|
||||
getNameOrAddress,
|
||||
getAssetInfo,
|
||||
getPublicKey,
|
||||
transferAsset,
|
||||
} from "../background";
|
||||
import { getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
||||
import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener";
|
||||
@ -75,6 +80,10 @@ import { fileToBase64 } from "../utils/fileReading";
|
||||
import { mimeToExtensionMap } from "../utils/memeTypes";
|
||||
import { RequestQueueWithPromise } from "../utils/queue/queue";
|
||||
import utils from "../utils/utils";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { isValidBase64WithDecode } from "../utils/decode";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 6 });
|
||||
|
||||
export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10);
|
||||
|
||||
@ -4886,4 +4895,367 @@ export const buyNameRequest = async (data, isFromExtension) => {
|
||||
} else {
|
||||
throw new Error("User declined request");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const multiPaymentWithPrivateData = async (data, isFromExtension) => {
|
||||
const requiredFields = ["payments", "assetId"];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
const resKeyPair = await getKeyPair();
|
||||
const parsedData = resKeyPair;
|
||||
const privateKey = parsedData.privateKey;
|
||||
const userPublicKey = parsedData.publicKey
|
||||
const {fee: paymentFee} = await getFee("TRANSFER_ASSET");
|
||||
const {fee: arbitraryFee} = await getFee("ARBITRARY");
|
||||
|
||||
let name = null
|
||||
const payments = data.payments;
|
||||
const assetId = data.assetId
|
||||
const pendingTransactions = []
|
||||
const pendingAdditionalArbitraryTxs = []
|
||||
const additionalArbitraryTxsWithoutPayment = data?.additionalArbitraryTxsWithoutPayment || []
|
||||
let totalAmount = 0
|
||||
let fee = 0
|
||||
for (const payment of payments) {
|
||||
const paymentRefId = uid.rnd();
|
||||
const requiredFieldsPayment = ["recipient", "amount"];
|
||||
|
||||
for (const field of requiredFieldsPayment) {
|
||||
if (!payment[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmReceiver = await getNameOrAddress(payment.recipient);
|
||||
if (confirmReceiver.error) {
|
||||
throw new Error("Invalid receiver address or name");
|
||||
}
|
||||
const receiverPublicKey = await getPublicKey(confirmReceiver)
|
||||
|
||||
const amount = +payment.amount.toFixed(8)
|
||||
|
||||
pendingTransactions.push({
|
||||
type: "PAYMENT",
|
||||
recipientAddress: confirmReceiver,
|
||||
amount: amount,
|
||||
paymentRefId,
|
||||
});
|
||||
|
||||
fee = fee + +paymentFee;
|
||||
totalAmount = totalAmount + amount;
|
||||
|
||||
if (payment.arbitraryTxs && payment.arbitraryTxs.length > 0) {
|
||||
for (const arbitraryTx of payment.arbitraryTxs) {
|
||||
const requiredFieldsArbitraryTx = ["service", "identifier", "base64"];
|
||||
|
||||
for (const field of requiredFieldsArbitraryTx) {
|
||||
if (!arbitraryTx[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
const getName = await getNameInfo();
|
||||
if (!getName) throw new Error("Name needed to publish");
|
||||
name = getName;
|
||||
}
|
||||
|
||||
const isValid = isValidBase64WithDecode(arbitraryTx.base64);
|
||||
if (!isValid) throw new Error("Invalid base64 data");
|
||||
if(!arbitraryTx?.service?.includes('_PRIVATE')) throw new Error('Please use a PRIVATE service')
|
||||
const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []
|
||||
pendingTransactions.push({
|
||||
type: "ARBITRARY",
|
||||
identifier: arbitraryTx.identifier,
|
||||
service: arbitraryTx.service,
|
||||
base64: arbitraryTx.base64,
|
||||
description: arbitraryTx?.description || "",
|
||||
paymentRefId,
|
||||
publicKeys: [receiverPublicKey, ...additionalPublicKeys]
|
||||
});
|
||||
|
||||
fee = fee + +arbitraryFee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalArbitraryTxsWithoutPayment && additionalArbitraryTxsWithoutPayment.length > 0) {
|
||||
for (const arbitraryTx of additionalArbitraryTxsWithoutPayment) {
|
||||
const requiredFieldsArbitraryTx = ["service", "identifier", "base64"];
|
||||
|
||||
for (const field of requiredFieldsArbitraryTx) {
|
||||
if (!arbitraryTx[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
const getName = await getNameInfo();
|
||||
if (!getName) throw new Error("Name needed to publish");
|
||||
name = getName;
|
||||
}
|
||||
|
||||
const isValid = isValidBase64WithDecode(arbitraryTx.base64);
|
||||
if (!isValid) throw new Error("Invalid base64 data");
|
||||
if(!arbitraryTx?.service?.includes('_PRIVATE')) throw new Error('Please use a PRIVATE service')
|
||||
const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []
|
||||
pendingAdditionalArbitraryTxs.push({
|
||||
type: "ARBITRARY",
|
||||
identifier: arbitraryTx.identifier,
|
||||
service: arbitraryTx.service,
|
||||
base64: arbitraryTx.base64,
|
||||
description: arbitraryTx?.description || "",
|
||||
publicKeys: additionalPublicKeys
|
||||
});
|
||||
|
||||
fee = fee + +arbitraryFee;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(!name) throw new Error('A name is needed to publish')
|
||||
const balance = await getBalanceInfo();
|
||||
|
||||
if(+balance < fee) throw new Error('Your QORT balance is insufficient')
|
||||
const assetBalance = await getAssetBalanceInfo(assetId)
|
||||
const assetInfo = await getAssetInfo(assetId)
|
||||
if(assetBalance < totalAmount) throw new Error('Your asset balance is insufficient')
|
||||
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: "Do you give this application permission to make the following payments and publishes?",
|
||||
text2: `Asset used in payments: ${assetInfo.name}`,
|
||||
html: `
|
||||
<div style="max-height: 30vh; overflow-y: auto;">
|
||||
<style>
|
||||
body {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.resource-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #444;
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
.resource-detail {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.resource-detail span {
|
||||
font-weight: bold;
|
||||
color: #bb86fc;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.resource-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.resource-detail {
|
||||
flex: 1 1 45%;
|
||||
margin-bottom: 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
${pendingTransactions.
|
||||
filter((item)=> item.type === 'PAYMENT').map(
|
||||
(payment) => `
|
||||
<div class="resource-container">
|
||||
<div class="resource-detail"><span>Recipient:</span> ${
|
||||
payment.recipientAddress
|
||||
}</div>
|
||||
<div class="resource-detail"><span>Amount:</span> ${payment.amount}</div>
|
||||
</div>`
|
||||
)
|
||||
.join("")}
|
||||
${[...pendingTransactions, ...pendingAdditionalArbitraryTxs].
|
||||
filter((item)=> item.type === 'ARBITRARY').map(
|
||||
(arbitraryTx) => `
|
||||
<div class="resource-container">
|
||||
<div class="resource-detail"><span>Service:</span> ${
|
||||
arbitraryTx.service
|
||||
}</div>
|
||||
<div class="resource-detail"><span>Name:</span> ${name}</div>
|
||||
<div class="resource-detail"><span>Identifier:</span> ${
|
||||
arbitraryTx.identifier
|
||||
}</div>
|
||||
</div>`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
|
||||
`,
|
||||
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
|
||||
}
|
35
src/transactions/TransferAssetTransaction.ts
Normal file
35
src/transactions/TransferAssetTransaction.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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 = {
|
||||
@ -35,6 +36,7 @@ export const transactionTypes = {
|
||||
7: BuyNameTransacion,
|
||||
8: CreatePollTransaction,
|
||||
9: VoteOnPollTransaction,
|
||||
12: TransferAssetTransaction,
|
||||
16: DeployAtTransaction,
|
||||
18: ChatTransaction,
|
||||
181: GroupChatTransaction,
|
||||
|
@ -13,4 +13,19 @@ export function decodeIfEncoded(input) {
|
||||
|
||||
// Return input as-is if not URI-encoded
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user