Skip to content

Commit

Permalink
Merge pull request #5022 from BitGo/WIN-3610-transfer-builder
Browse files Browse the repository at this point in the history
feat(sdk-coin-xrp): add token transaction builders
  • Loading branch information
hitansh-bitgo authored Oct 28, 2024
2 parents 85f39c4 + 9c23193 commit 6fed319
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 16 deletions.
6 changes: 3 additions & 3 deletions modules/sdk-coin-xrp/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
VerifyAddressOptions as BaseVerifyAddressOptions,
TransactionPrebuild,
} from '@bitgo/sdk-core';
import { AccountSet, Payment, Signer, SignerEntry, SignerListSet } from 'xrpl';
import { AccountSet, Amount, Payment, Signer, SignerEntry, SignerListSet, TrustSet } from 'xrpl';

export enum XrpTransactionType {
AccountSet = 'AccountSet',
Expand All @@ -14,7 +14,7 @@ export enum XrpTransactionType {
TrustSet = 'TrustSet',
}

export type XrpTransaction = Payment | AccountSet | SignerListSet;
export type XrpTransaction = Payment | AccountSet | SignerListSet | TrustSet;

export interface Address {
address: string;
Expand Down Expand Up @@ -115,7 +115,7 @@ export interface TxData {
// transfer xrp fields
destination?: string;
destinationTag?: number;
amount?: string;
amount?: Amount;
// account set fields
messageKey?: string;
setFlag?: number;
Expand Down
4 changes: 3 additions & 1 deletion modules/sdk-coin-xrp/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Utils from './utils';

export { AccountSetBuilder } from './accountSetBuilder';
export * from './constants';
export * from './iface';
export { AccountSetBuilder } from './accountSetBuilder';
export { KeyPair } from './keyPair';
export { TokenTransferBuilder } from './tokenTransferBuilder';
export { Transaction } from './transaction';
export { TransactionBuilder } from './transactionBuilder';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { TransferBuilder } from './transferBuilder';
export { TrustSetBuilder } from './trustsetBuilder';
export { WalletInitializationBuilder } from './walletInitializationBuilder';
export { Utils };
103 changes: 103 additions & 0 deletions modules/sdk-coin-xrp/src/lib/tokenTransferBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import { Amount, Payment } from 'xrpl';
import { XrpTransactionType } from './iface';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import utils from './utils';

export class TokenTransferBuilder extends TransactionBuilder {
private _amount: Amount;
private _destination: string;
private _destinationTag?: number;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.Send;
}

protected get xrpTransactionType(): XrpTransactionType.Payment {
return XrpTransactionType.Payment;
}

initBuilder(tx: Transaction): void {
super.initBuilder(tx);

const { destination, amount, destinationTag } = tx.toJson();
if (!destination) {
throw new BuildTransactionError('Missing destination');
}
if (!amount) {
throw new BuildTransactionError('Missing amount');
}

const normalizeAddress = utils.normalizeAddress({ address: destination, destinationTag });
this.to(normalizeAddress);
if (!utils.isIssuedCurrencyAmount(amount)) {
throw new BuildTransactionError('Invalid Amount');
}
const amountBigNum = BigNumber(amount.value).shiftedBy(this._coinConfig.decimalPlaces);
this.amount(amountBigNum.toFixed());
}

/**
* Set the receiver address
* @param {string} address - the address with optional destination tag
* @returns {TransactionBuilder} This transaction builder
*/
to(address: string): TransactionBuilder {
const { address: xrpAddress, destinationTag } = utils.getAddressDetails(address);
this._destination = xrpAddress;
this._destinationTag = destinationTag;
return this;
}

/**
* Set the amount to send
* @param {string} amount - the amount sent
* @returns {TransactionBuilder} This transaction builder
*/
amount(amount: string): TransactionBuilder {
if (typeof amount !== 'string') {
throw new Error(`amount type ${typeof amount} must be a string`);
}
const amountBigNum = BigNumber(amount);
if (amountBigNum.lt(0)) {
throw new Error(`amount ${amount} is not valid`);
}
const currency = utils.getXrpCurrencyFromTokenName(this._coinConfig.name);
// Unlike most coins, XRP Token amounts are represented in decimal notation
const value = amountBigNum.shiftedBy(-1 * this._coinConfig.decimalPlaces).toFixed();
this._amount = {
value: value,
...currency,
};
return this;
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
if (!this._sender) {
throw new BuildTransactionError('Sender must be set before building the transaction');
}

const transferFields: Payment = {
TransactionType: this.xrpTransactionType,
Account: this._sender,
Destination: this._destination,
Amount: this._amount,
};

if (typeof this._destinationTag === 'number') {
transferFields.DestinationTag = this._destinationTag;
}

this._specificFields = transferFields;

return await super.buildImplementation();
}
}
29 changes: 21 additions & 8 deletions modules/sdk-coin-xrp/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics';
import utils from './utils';

import BigNumber from 'bignumber.js';
import { Signer } from 'xrpl/dist/npm/models/common';
import { Signer } from 'xrpl';
import {
AccountSetTransactionExplanation,
SignerListSetTransactionExplanation,
Expand Down Expand Up @@ -81,10 +81,12 @@ export class Transaction extends BaseTransaction {
case XrpTransactionType.Payment:
txData.destination = this._xrpTransaction.Destination;
txData.destinationTag = this._xrpTransaction.DestinationTag;
if (_.isString(this._xrpTransaction.Amount)) {
if (
typeof this._xrpTransaction.Amount === 'string' ||
utils.isIssuedCurrencyAmount(this._xrpTransaction.Amount)
) {
txData.amount = this._xrpTransaction.Amount;
} else {
// Amount is an object
throw new InvalidTransactionError('Invalid amount');
}
return txData;
Expand All @@ -99,6 +101,10 @@ export class Transaction extends BaseTransaction {
txData.signerEntries = this._xrpTransaction.SignerEntries;
return txData;

case XrpTransactionType.TrustSet:
txData.amount = this._xrpTransaction.LimitAmount;
return txData;

default:
throw new InvalidTransactionError('Invalid transaction type');
}
Expand Down Expand Up @@ -313,7 +319,15 @@ export class Transaction extends BaseTransaction {
this.setTransactionType(TransactionType.AccountUpdate);
break;
case XrpTransactionType.Payment:
this.setTransactionType(TransactionType.Send);
if (utils.isIssuedCurrencyAmount(this._xrpTransaction.Amount)) {
this.setTransactionType(TransactionType.SendToken);
} else {
this.setTransactionType(TransactionType.Send);
}
break;
case XrpTransactionType.TrustSet:
this.setTransactionType(TransactionType.TrustLine);
break;
}
this.loadInputsAndOutputs();
}
Expand All @@ -326,15 +340,14 @@ export class Transaction extends BaseTransaction {
return;
}
if (this._xrpTransaction.TransactionType === XrpTransactionType.Payment) {
let value: string, coin: string;
let value: string;
const { Account, Destination, Amount, DestinationTag } = this._xrpTransaction;
if (_.isString(Amount)) {
if (typeof Amount === 'string') {
value = Amount;
coin = this._coinConfig.name;
} else {
value = Amount.value;
coin = Amount.currency;
}
const coin = this._coinConfig.name;
this.inputs.push({
address: Account,
value,
Expand Down
16 changes: 16 additions & 0 deletions modules/sdk-coin-xrp/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import xrpl from 'xrpl';
import { AccountSetBuilder } from './accountSetBuilder';
import { TokenTransferBuilder } from './tokenTransferBuilder';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { TrustSetBuilder } from './trustsetBuilder';
import utils from './utils';
import { WalletInitializationBuilder } from './walletInitializationBuilder';

Expand Down Expand Up @@ -32,6 +34,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.getTransferBuilder(tx);
case TransactionType.WalletInitialization:
return this.getWalletInitializationBuilder(tx);
case TransactionType.SendToken:
return this.getTokenTransferBuilder(tx);
case TransactionType.TrustLine:
return this.getTrustSetBuilder(tx);
default:
throw new InvalidTransactionError('Invalid transaction');
}
Expand All @@ -55,6 +61,16 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.initializeBuilder(tx, new AccountSetBuilder(this._coinConfig));
}

/** @inheritdoc */
public getTokenTransferBuilder(tx?: Transaction): TokenTransferBuilder {
return this.initializeBuilder(tx, new TokenTransferBuilder(this._coinConfig));
}

/** @inheritdoc */
public getTrustSetBuilder(tx?: Transaction): TrustSetBuilder {
return this.initializeBuilder(tx, new TrustSetBuilder(this._coinConfig));
}

/**
* Initialize the builder with the given transaction
*
Expand Down
8 changes: 5 additions & 3 deletions modules/sdk-coin-xrp/src/lib/transferBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { Payment } from 'xrpl';
import { Amount } from 'xrpl/dist/npm/models/common';
import { Amount, Payment } from 'xrpl';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import utils from './utils';
Expand Down Expand Up @@ -36,6 +35,9 @@ export class TransferBuilder extends TransactionBuilder {

const normalizeAddress = utils.normalizeAddress({ address: destination, destinationTag });
this.to(normalizeAddress);
if (typeof amount !== 'string') {
throw new BuildTransactionError('Invalid Amount');
}
this.amount(amount);
}

Expand Down Expand Up @@ -81,7 +83,7 @@ export class TransferBuilder extends TransactionBuilder {
Amount: this._amount,
};

if (this._destinationTag) {
if (typeof this._destinationTag === 'number') {
transferFields.DestinationTag = this._destinationTag;
}

Expand Down
79 changes: 79 additions & 0 deletions modules/sdk-coin-xrp/src/lib/trustsetBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import { IssuedCurrencyAmount, TrustSet } from 'xrpl';
import { XrpTransactionType } from './iface';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import utils from './utils';

export class TrustSetBuilder extends TransactionBuilder {
private _amount: IssuedCurrencyAmount;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.TrustLine;
}

protected get xrpTransactionType(): XrpTransactionType.TrustSet {
return XrpTransactionType.TrustSet;
}

initBuilder(tx: Transaction): void {
super.initBuilder(tx);

const { amount } = tx.toJson();
if (!amount) {
throw new BuildTransactionError('Missing amount');
}
if (!utils.isIssuedCurrencyAmount(amount)) {
throw new BuildTransactionError('Invalid Limit Amount');
}
// The amount is represented in decimal notation, so we need to multiply it by the decimal places
const amountBigNum = BigNumber(amount.value).shiftedBy(this._coinConfig.decimalPlaces);
this.amount(amountBigNum.toFixed());
}

/**
* Set the amount to send
* @param {string} amount - the amount sent
* @returns {TransactionBuilder} This transaction builder
*/
amount(amount: string): TransactionBuilder {
if (typeof amount !== 'string') {
throw new Error(`amount type ${typeof amount} must be a string`);
}
const amountBigNum = BigNumber(amount);
if (amountBigNum.lt(0)) {
throw new Error(`amount ${amount} is not valid`);
}
const currency = utils.getXrpCurrencyFromTokenName(this._coinConfig.name);
// Unlike most coins, XRP Token amounts are represented in decimal notation
const value = amountBigNum.shiftedBy(-1 * this._coinConfig.decimalPlaces).toFixed();
this._amount = {
value: value,
...currency,
};
return this;
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
if (!this._sender) {
throw new BuildTransactionError('Sender must be set before building the transaction');
}

const trustSetFields: TrustSet = {
TransactionType: this.xrpTransactionType,
Account: this._sender,
LimitAmount: this._amount,
};

this._specificFields = trustSetFields;

return await super.buildImplementation();
}
}
Loading

0 comments on commit 6fed319

Please sign in to comment.