diff --git a/client.js b/client.js index e260ec9f..5089e57e 100644 --- a/client.js +++ b/client.js @@ -1027,6 +1027,7 @@ vorpal.command('transfer', 'transfers tokens to another account').action(async f chatId: calculateChatId(to, USER.address), memo: answers.memo ? answers.memo : null, timestamp: Date.now(), + // deductTxFeeFromAmount: true, //test: false } signTransaction(tx) diff --git a/docs/API.md b/docs/API.md index 9cce4346..3763f9ee 100644 --- a/docs/API.md +++ b/docs/API.md @@ -487,6 +487,7 @@ Transfers tokens between accounts. "from": string, // Source account ID "to": string, // Target account ID "amount": bigint, // Amount to transfer (must be > 0) + "deductTxFeeFromAmount": boolean, // Optional, defaults to false. Supported from version 2.4.9. "timestamp": number, "sign": { "owner": string // Must match 'from' field @@ -499,15 +500,16 @@ Requirements: - Amount must be greater than 0 - Source account must have sufficient balance to cover: - Transfer amount - - Transaction fee - - Maintenance amount + - Transaction fee, unless `deductTxFeeFromAmount` is `true` +- If `deductTxFeeFromAmount` is `true`, the transfer amount must be greater than the transaction fee - Must be signed by the source account - Signature must be cryptographically valid The transaction will: -1. Deduct amount + fees from source account -2. Add amount to target account -3. Update timestamps for both accounts +1. Deduct amount from source account +2. Add amount to target account, minus transaction fee when `deductTxFeeFromAmount` is `true` +3. Deduct transaction fee from source account when `deductTxFeeFromAmount` is false or omitted +4. Update timestamps for both accounts #### `email` ⚠️ DEPRECATED **Deprecated in version 2.5.0** - This transaction type is deprecated and will be removed in a future version. New transactions of this type will be rejected when network version >= 2.5.0. diff --git a/src/@types/index.ts b/src/@types/index.ts index a02d4e95..a19a535d 100644 --- a/src/@types/index.ts +++ b/src/@types/index.ts @@ -415,6 +415,7 @@ export namespace Tx { } chatId: string fee?: bigint // Optional fee for the transfer + deductTxFeeFromAmount?: boolean // Optional, defaults to false. If true, tx fee is deducted from the transfer amount. } export interface Verify extends BaseLiberdusTx { diff --git a/src/@types/transactionSchemas.ts b/src/@types/transactionSchemas.ts index 854456bd..06128b2c 100644 --- a/src/@types/transactionSchemas.ts +++ b/src/@types/transactionSchemas.ts @@ -98,6 +98,8 @@ export const schemaTransferTX = { amount: { isBigInt: true }, memo: { type: ['string', 'null'] }, chatId: { type: 'string' }, + fee: { isBigInt: true }, + deductTxFeeFromAmount: { type: 'boolean' }, }, required: [...baseTxRequired, 'from', 'to', 'amount', 'chatId'], additionalProperties: false, diff --git a/src/config/index.ts b/src/config/index.ts index 2a136139..82be7b46 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -253,6 +253,7 @@ interface LiberdusFlags { weiToLibStringFormat: boolean includeTxToKeyInReadTx: boolean updateTollRequiredTxInChatHistory: boolean + supportDeductTxFeeFromAmount: boolean } } @@ -298,6 +299,7 @@ export const LiberdusFlags: LiberdusFlags = { weiToLibStringFormat: true, // turn on by 2.4.8 includeTxToKeyInReadTx: true, // turn on by 2.4.8 updateTollRequiredTxInChatHistory: false, // turn on by 2.4.9 + supportDeductTxFeeFromAmount: false, // turn on by 2.4.9 }, } diff --git a/src/transactions/transfer.ts b/src/transactions/transfer.ts index c08943cb..adbef39d 100644 --- a/src/transactions/transfer.ts +++ b/src/transactions/transfer.ts @@ -37,6 +37,19 @@ export const validate_fields = (tx: Tx.Transfer, response: ShardusTypes.Incoming response.reason = `tx "memo" size must be less than ${config.LiberdusFlags.transferMemoLimit} characters.` return response } + if (config.LiberdusFlags.versionFlags.supportDeductTxFeeFromAmount === true) { + if (tx.deductTxFeeFromAmount !== undefined && typeof tx.deductTxFeeFromAmount !== 'boolean') { + response.reason = 'tx "deductTxFeeFromAmount" field must be a boolean.' + return response + } + if (tx.deductTxFeeFromAmount === true) { + const transactionFee = utils.getTransactionFeeWei(AccountsStorage.cachedNetworkAccount) + if (tx.amount <= transactionFee) { + response.reason = 'transfer amount must be greater than the transaction fee when deductTxFeeFromAmount is true' + return response + } + } + } if (!tx.sign || !tx.sign.owner || !tx.sign.sig || tx.sign.owner !== tx.from) { response.reason = 'not signed by from account' return response @@ -89,10 +102,25 @@ export const validate = ( return response } - if (from.data.balance < tx.amount + utils.getTransactionFeeWei(AccountsStorage.cachedNetworkAccount)) { + const transactionFee = utils.getTransactionFeeWei(AccountsStorage.cachedNetworkAccount) + const deductTxFeeFromAmount = + config.LiberdusFlags.versionFlags.supportDeductTxFeeFromAmount === true && + tx.deductTxFeeFromAmount === true + + if (deductTxFeeFromAmount === false && from.data.balance < tx.amount + transactionFee) { response.reason = "from account doesn't have sufficient balance to cover the transaction" return response } + if (deductTxFeeFromAmount === true) { + if (from.data.balance < tx.amount) { + response.reason = "from account doesn't have sufficient balance to cover the transfer amount" + return response + } + if (tx.amount <= transactionFee) { + response.reason = 'transfer amount must be greater than the transaction fee when deductTxFeeFromAmount is true' + return response + } + } // if there is a memo, check if the amount is larger than the Toll required for the chat if (config.LiberdusFlags.versionFlags.minTransferAmountCheck) { const hasMemo = (tx.memo && tx.memo.length > 0) || (tx.xmemo && tx.xmemo.message && tx.xmemo.message.length > 0) @@ -160,10 +188,19 @@ export const apply = ( // update balances const transactionFee = utils.getTransactionFeeWei(AccountsStorage.cachedNetworkAccount) const maintenanceFee = utils.maintenanceAmount(txTimestamp, from, network) - from.data.balance = SafeBigIntMath.subtract(from.data.balance, transactionFee) + const deductTxFeeFromAmount = + config.LiberdusFlags.versionFlags.supportDeductTxFeeFromAmount === true && + tx.deductTxFeeFromAmount === true + if (deductTxFeeFromAmount === false) { + from.data.balance = SafeBigIntMath.subtract(from.data.balance, transactionFee) + } from.data.balance = SafeBigIntMath.subtract(from.data.balance, maintenanceFee) from.data.balance = SafeBigIntMath.subtract(from.data.balance, tx.amount) - to.data.balance = SafeBigIntMath.add(to.data.balance, tx.amount) + let amountReceived = tx.amount + if (deductTxFeeFromAmount === true) { + amountReceived = SafeBigIntMath.subtract(tx.amount, transactionFee) + } + to.data.balance = SafeBigIntMath.add(to.data.balance, amountReceived) // store transfer data in chat if (!from.data.chats[tx.to]) { @@ -184,6 +221,15 @@ export const apply = ( to.timestamp = txTimestamp chat.timestamp = txTimestamp + const additionalInfo: { amount: bigint; maintenanceFee: bigint } = { + amount: tx.amount, + maintenanceFee, + } + if (deductTxFeeFromAmount === true) { + // amount reflects the net amount received by the recipient. + additionalInfo.amount = amountReceived + } + const appReceiptData: AppReceiptData = { txId, timestamp: txTimestamp, @@ -192,10 +238,7 @@ export const apply = ( to: tx.to, type: tx.type, transactionFee, - additionalInfo: { - amount: tx.amount, - maintenanceFee, - }, + additionalInfo, } const appReceiptDataHash = crypto.hashObj(appReceiptData) dapp.applyResponseAddReceiptData(applyResponse, appReceiptData, appReceiptDataHash) diff --git a/src/versioning/migrations/2.4.9.ts b/src/versioning/migrations/2.4.9.ts index 1cda4be1..673b4be9 100644 --- a/src/versioning/migrations/2.4.9.ts +++ b/src/versioning/migrations/2.4.9.ts @@ -7,4 +7,5 @@ export const migrate: Migration = async () => { nestedCountersInstance.countEvent('migrate', 'calling migrate 2.4.9') LiberdusFlags.versionFlags.updateTollRequiredTxInChatHistory = true + LiberdusFlags.versionFlags.supportDeductTxFeeFromAmount = true }