From d9a42c866968a91f21e1a1fce1e744e60bf2ea5a Mon Sep 17 00:00:00 2001 From: Nathan Nichols Date: Fri, 30 Jul 2021 11:16:01 -0700 Subject: [PATCH] Allow XAddress Issuers (#1471) * feat: Allow clients to use XAddresses for issuers --- src/common/utils.ts | 26 ++++++++++++----- src/transaction/order.ts | 6 ++-- src/transaction/payment.ts | 3 +- src/transaction/utils.ts | 23 +++++++++++++++ test/api/prepareTransaction/index.ts | 28 +++++++++++++++++++ test/api/prepareTrustline/index.ts | 9 ++++++ test/fixtures/requests/index.js | 3 +- .../prepare-trustline-issuer-xaddress.json | 6 ++++ test/fixtures/responses/index.js | 1 + .../prepare-trustline-issuer-xaddress.json | 9 ++++++ 10 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 test/fixtures/requests/prepare-trustline-issuer-xaddress.json create mode 100644 test/fixtures/responses/prepare-trustline-issuer-xaddress.json diff --git a/src/common/utils.ts b/src/common/utils.ts index b5964930..46f1b846 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,8 +1,9 @@ import * as _ from 'lodash' import BigNumber from 'bignumber.js' import {deriveKeypair} from 'ripple-keypairs' -import {Amount, RippledAmount} from './types/objects' +import {RippledAmount} from './types/objects' import {ValidationError} from './errors' +import {xAddressToClassicAddress} from 'ripple-address-codec' function isValidSecret(secret: string): boolean { try { @@ -105,20 +106,31 @@ function xrpToDrops(xrp: BigNumber.Value): string { .toString(10) } -function toRippledAmount(amount: Amount): RippledAmount { +function toRippledAmount(amount: RippledAmount): RippledAmount { + if (typeof amount === 'string') + return amount; + if (amount.currency === 'XRP') { return xrpToDrops(amount.value) } if (amount.currency === 'drops') { return amount.value } + + let issuer = amount.counterparty || amount.issuer + let tag: number | false = false; + + try { + ({classicAddress: issuer, tag} = xAddressToClassicAddress(issuer)) + } catch (e) { /* not an X-address */ } + + if (tag !== false) { + throw new ValidationError("Issuer X-address includes a tag") + } + return { currency: amount.currency, - issuer: amount.counterparty - ? amount.counterparty - : amount.issuer - ? amount.issuer - : undefined, + issuer, value: amount.value } } diff --git a/src/transaction/order.ts b/src/transaction/order.ts index 4e979d5c..6dc125ca 100644 --- a/src/transaction/order.ts +++ b/src/transaction/order.ts @@ -1,6 +1,6 @@ import * as utils from './utils' const offerFlags = utils.common.txFlags.OfferCreate -import {validate, iso8601ToRippleTime} from '../common' +import {validate, iso8601ToRippleTime, toRippledAmount} from '../common' import {Instructions, Prepare, OfferCreateTransaction} from './types' import {FormattedOrderSpecification} from '../common/types/objects/index' import {RippleAPI} from '..' @@ -9,10 +9,10 @@ function createOrderTransaction( account: string, order: FormattedOrderSpecification ): OfferCreateTransaction { - const takerPays = utils.common.toRippledAmount( + const takerPays = toRippledAmount( order.direction === 'buy' ? order.quantity : order.totalPrice ) - const takerGets = utils.common.toRippledAmount( + const takerGets = toRippledAmount( order.direction === 'buy' ? order.totalPrice : order.quantity ) diff --git a/src/transaction/payment.ts b/src/transaction/payment.ts index c0b03144..b94cb192 100644 --- a/src/transaction/payment.ts +++ b/src/transaction/payment.ts @@ -1,7 +1,6 @@ import * as _ from 'lodash' import * as utils from './utils' const validate = utils.common.validate -const toRippledAmount = utils.common.toRippledAmount const paymentFlags = utils.common.txFlags.Payment const ValidationError = utils.common.errors.ValidationError import {Instructions, Prepare, TransactionJSON} from './types' @@ -12,7 +11,7 @@ import { MinAdjustment, Memo } from '../common/types/objects' -import {xrpToDrops} from '../common' +import {toRippledAmount, xrpToDrops} from '../common' import {RippleAPI} from '..' import {getClassicAccountAndTag, ClassicAccountAndTag} from './utils' diff --git a/src/transaction/utils.ts b/src/transaction/utils.ts index 3a256c5d..9ce88e74 100644 --- a/src/transaction/utils.ts +++ b/src/transaction/utils.ts @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js' import * as common from '../common' import {Memo} from '../common/types/objects' import {Instructions, Prepare, TransactionJSON} from './types' +import {toRippledAmount} from '../common' import {RippleAPI} from '..' import {ValidationError} from '../common/errors' import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec' @@ -200,6 +201,16 @@ function prepareTransaction( } } + function convertIssuedCurrencyToAccountIfPresent(fieldName: string): void { + const amount = txJSON[fieldName] + if (typeof amount === 'number' + || amount instanceof Array + || amount == null) + return + + newTxJSON[fieldName] = toRippledAmount(amount) + } + // DepositPreauth: convertToClassicAccountIfPresent('Authorize') convertToClassicAccountIfPresent('Unauthorize') @@ -210,6 +221,18 @@ function prepareTransaction( // SetRegularKey: convertToClassicAccountIfPresent('RegularKey') + // Payment + convertIssuedCurrencyToAccountIfPresent('Amount') + convertIssuedCurrencyToAccountIfPresent('SendMax') + convertIssuedCurrencyToAccountIfPresent('DeliverMin') + + // OfferCreate + convertIssuedCurrencyToAccountIfPresent('TakerPays') + convertIssuedCurrencyToAccountIfPresent('TakerGets') + + // TrustSet + convertIssuedCurrencyToAccountIfPresent('LimitAmount') + setCanonicalFlag(newTxJSON) function prepareMaxLedgerVersion(): Promise { diff --git a/test/api/prepareTransaction/index.ts b/test/api/prepareTransaction/index.ts index eece89cb..146a76a6 100644 --- a/test/api/prepareTransaction/index.ts +++ b/test/api/prepareTransaction/index.ts @@ -1075,6 +1075,34 @@ export default { assertResultMatch(response, expectedResponse, 'prepare') }, + + 'xaddress-issuer': async (api, address) => { + const localInstructions = { + ...instructionsWithMaxLedgerVersionOffset, + maxFee: '0.000012' + } + + const txJSON = { + TransactionType: 'Payment', + Account: address, + Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo', + Amount: { + currency: 'USD', + issuer: 'XVbehP2sFMQAd5orFAy8Lt6vLHGiDhA7VMAnsv9H6WpuB1s', + value: '0.01' + }, + SendMax: { + currency: 'USD', + issuer: 'XVbehP2sFMQAd5orFAy8Lt6vLHGiDhA7VMAnsv9H6WpuB1s', + value: '0.01' + }, + Flags: 0 + } + + const response = await api.prepareTransaction(txJSON, localInstructions) + assertResultMatch(response, responses.preparePayment.normal, 'prepare') + }, + 'PaymentChannelCreate': async (api, address) => { const localInstructions = { ...instructionsWithMaxLedgerVersionOffset, diff --git a/test/api/prepareTrustline/index.ts b/test/api/prepareTrustline/index.ts index 4a7cef45..278b6af1 100644 --- a/test/api/prepareTrustline/index.ts +++ b/test/api/prepareTrustline/index.ts @@ -50,6 +50,15 @@ export default { ) }, + 'xaddress-issuer': async (api, address) => { + const result = await api.prepareTrustline( + address, + requests.prepareTrustline.issuedXAddress, + instructionsWithMaxLedgerVersionOffset + ) + assertResultMatch(result, responses.prepareTrustline.issuedXAddress, 'prepare') + }, + 'with ticket': async (api, address) => { const localInstructions = { ...instructionsWithMaxLedgerVersionOffset, diff --git a/test/fixtures/requests/index.js b/test/fixtures/requests/index.js index ae583668..a57fd716 100644 --- a/test/fixtures/requests/index.js +++ b/test/fixtures/requests/index.js @@ -72,7 +72,8 @@ module.exports = { prepareTrustline: { simple: require('./prepare-trustline-simple'), complex: require('./prepare-trustline'), - frozen: require('./prepare-trustline-frozen.json') + frozen: require('./prepare-trustline-frozen.json'), + issuedXAddress: require('./prepare-trustline-issuer-xaddress.json') }, sign: { normal: require('./sign'), diff --git a/test/fixtures/requests/prepare-trustline-issuer-xaddress.json b/test/fixtures/requests/prepare-trustline-issuer-xaddress.json new file mode 100644 index 00000000..ae9821db --- /dev/null +++ b/test/fixtures/requests/prepare-trustline-issuer-xaddress.json @@ -0,0 +1,6 @@ +{ + "currency": "BTC", + "counterparty": "XVbehP2sFMQAd5orFAy8Lt6vLHGiDhA7VMAnsv9H6WpuB1s", + "limit": "0.1" +} + \ No newline at end of file diff --git a/test/fixtures/responses/index.js b/test/fixtures/responses/index.js index 08cfbe2b..18646f3e 100644 --- a/test/fixtures/responses/index.js +++ b/test/fixtures/responses/index.js @@ -204,6 +204,7 @@ module.exports = { simple: require('./prepare-trustline-simple'), ticket: require('./prepare-trustline-ticket'), frozen: require('./prepare-trustline-frozen'), + issuedXAddress: require('./prepare-trustline-issuer-xaddress.json'), complex: require('./prepare-trustline') }, sign: { diff --git a/test/fixtures/responses/prepare-trustline-issuer-xaddress.json b/test/fixtures/responses/prepare-trustline-issuer-xaddress.json new file mode 100644 index 00000000..70c98bfd --- /dev/null +++ b/test/fixtures/responses/prepare-trustline-issuer-xaddress.json @@ -0,0 +1,9 @@ +{ + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"value\":\"0.1\",\"currency\":\"BTC\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} + \ No newline at end of file