Skip to content

Commit

Permalink
feat(sdk-coin-apt): changes to accomodate suggested improvements
Browse files Browse the repository at this point in the history
Ticket: Coin-2894

Focus-
* fungible transfer (apt, legacy & nft corrected in separate pr)

Included-
* assetId change
* parseTransactionPayload
* validRawTxn
* associated changes
  • Loading branch information
baltiyal committed Jan 27, 2025
1 parent 307e70b commit 7e0ac97
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 222 deletions.
10 changes: 2 additions & 8 deletions modules/sdk-coin-apt/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface AptTransactionExplanation extends BaseTransactionExplanation {
/**
* The transaction data returned from the toJson() function of a transaction
*/
export interface TransferTxData {
export interface TxData {
id: string;
sender: string;
recipient: TransactionRecipient;
Expand All @@ -22,11 +22,5 @@ export interface TransferTxData {
gasUsed: number;
expirationTime: number;
feePayer: string;
}

/**
* The token transaction data returned from the toJson() function of a transaction
*/
export interface FungibleTokenTransferTxData extends TransferTxData {
fungibleTokenAddress: string;
assetId?: string;
}
Original file line number Diff line number Diff line change
@@ -1,102 +1,46 @@
import { Transaction } from './transaction';
import {
AccountAddress,
AccountAuthenticatorEd25519,
Aptos,
AptosConfig,
EntryFunctionABI,
Network,
parseTypeTag,
SignedTransaction,
TransactionAuthenticatorFeePayer,
TransactionPayload,
TransactionPayloadEntryFunction,
TypeTagAddress,
TypeTagU64,
} from '@aptos-labs/ts-sdk';
import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { FungibleTokenTransferTxData } from '../iface';
import utils from '../utils';
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';

export class FungibleAssetTransaction extends Transaction {
protected _fungibleTokenAddress: string;

constructor(coinConfig: Readonly<CoinConfig>) {
super(coinConfig);
this._type = TransactionType.SendToken;
this._fungibleTokenAddress = AccountAddress.ZERO.toString();
}

get fungibleTokenAddress(): string {
return this._fungibleTokenAddress;
}

set fungibleTokenAddress(value: string) {
this._fungibleTokenAddress = value;
}

toJson(): FungibleTokenTransferTxData {
return {
id: this.id,
sender: this.sender,
recipient: this.recipient,
sequenceNumber: this.sequenceNumber,
maxGasAmount: this.maxGasAmount,
gasUnitPrice: this.gasUnitPrice,
gasUsed: this.gasUsed,
expirationTime: this.expirationTime,
feePayer: this.feePayerAddress,
fungibleTokenAddress: this.fungibleTokenAddress,
};
this._assetId = AccountAddress.ZERO.toString();
}

protected getRecipientFromTransactionPayload(payload: TransactionPayload): TransactionRecipient {
let address = 'INVALID';
let amount = '0';
if (payload instanceof TransactionPayloadEntryFunction) {
const entryFunction = payload.entryFunction;
address = entryFunction.args[1].toString();
const amountBuffer = Buffer.from(entryFunction.args[2].bcsToBytes());
amount = amountBuffer.readBigUint64LE().toString();
protected parseTransactionPayload(payload: TransactionPayload): void {
if (!(payload instanceof TransactionPayloadEntryFunction) || payload.entryFunction.args.length !== 3) {
throw new InvalidTransactionError('Invalid transaction payload');
}
return { address, amount };
}

fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void {
try {
const rawTxn = signedTxn.raw_txn;
this._sender = rawTxn.sender.toString();
this._recipient = this.getRecipientFromTransactionPayload(rawTxn.payload);
this._fungibleTokenAddress = utils.getFungibleTokenAddressFromTransactionPayload(rawTxn.payload);
this._sequenceNumber = utils.castToNumber(rawTxn.sequence_number);
this._maxGasAmount = utils.castToNumber(rawTxn.max_gas_amount);
this._gasUnitPrice = utils.castToNumber(rawTxn.gas_unit_price);
this._expirationTime = utils.castToNumber(rawTxn.expiration_timestamp_secs);
this._rawTransaction = rawTxn;

this.loadInputsAndOutputs();
const authenticator = signedTxn.authenticator as TransactionAuthenticatorFeePayer;
this._feePayerAddress = authenticator.fee_payer.address.toString();
const senderAuthenticator = authenticator.sender as AccountAuthenticatorEd25519;
const senderSignature = Buffer.from(senderAuthenticator.signature.toUint8Array());
this.addSenderSignature({ pub: senderAuthenticator.public_key.toString() }, senderSignature);

const feePayerAuthenticator = authenticator.fee_payer.authenticator as AccountAuthenticatorEd25519;
const feePayerSignature = Buffer.from(feePayerAuthenticator.signature.toUint8Array());
this.addFeePayerSignature({ pub: feePayerAuthenticator.public_key.toString() }, feePayerSignature);
} catch (e) {
console.error('invalid signed transaction', e);
throw new Error('invalid signed transaction');
const entryFunction = payload.entryFunction;
if (!this._recipient) {
this._recipient = {} as TransactionRecipient;
}
this._assetId = entryFunction.args[0].toString();
this._recipient.address = entryFunction.args[1].toString();
const amountBuffer = Buffer.from(entryFunction.args[2].bcsToBytes());
this._recipient.amount = amountBuffer.readBigUint64LE().toString();
}

protected async buildRawTransaction(): Promise<void> {
const network: Network = this._coinConfig.network.type === NetworkType.MAINNET ? Network.MAINNET : Network.TESTNET;
const aptos = new Aptos(new AptosConfig({ network }));
const senderAddress = AccountAddress.fromString(this._sender);
const recipientAddress = AccountAddress.fromString(this._recipient.address);
const fungibleTokenAddress = this._fungibleTokenAddress;
const fungibleTokenAddress = this._assetId;

const faTransferAbi: EntryFunctionABI = {
typeParameters: [{ constraints: [] }],
Expand Down
49 changes: 46 additions & 3 deletions modules/sdk-coin-apt/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import {
SignedTransaction,
SimpleTransaction,
TransactionAuthenticatorFeePayer,
TransactionPayload,
} from '@aptos-labs/ts-sdk';
import { DEFAULT_GAS_UNIT_PRICE, SECONDS_PER_WEEK, UNAVAILABLE_TEXT } from '../constants';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { AptTransactionExplanation } from '../iface';
import { AptTransactionExplanation, TxData } from '../iface';

export abstract class Transaction extends BaseTransaction {
protected _rawTransaction: RawTransaction;
Expand All @@ -42,7 +43,7 @@ export abstract class Transaction extends BaseTransaction {
protected _gasUsed: number;
protected _expirationTime: number;
protected _feePayerAddress: string;
private _assetId: string;
protected _assetId: string;

static EMPTY_PUBLIC_KEY = Buffer.alloc(32);
static EMPTY_SIGNATURE = Buffer.alloc(64);
Expand Down Expand Up @@ -150,7 +151,34 @@ export abstract class Transaction extends BaseTransaction {

protected abstract buildRawTransaction(): void;

protected abstract fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void;
protected abstract parseTransactionPayload(payload: TransactionPayload): void;

fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void {
try {
const rawTxn = signedTxn.raw_txn;
this.parseTransactionPayload(rawTxn.payload);
this._sender = rawTxn.sender.toString();
this._sequenceNumber = utils.castToNumber(rawTxn.sequence_number);
this._maxGasAmount = utils.castToNumber(rawTxn.max_gas_amount);
this._gasUnitPrice = utils.castToNumber(rawTxn.gas_unit_price);
this._expirationTime = utils.castToNumber(rawTxn.expiration_timestamp_secs);
this._rawTransaction = rawTxn;

this.loadInputsAndOutputs();
const authenticator = signedTxn.authenticator as TransactionAuthenticatorFeePayer;
this._feePayerAddress = authenticator.fee_payer.address.toString();
const senderAuthenticator = authenticator.sender as AccountAuthenticatorEd25519;
const senderSignature = Buffer.from(senderAuthenticator.signature.toUint8Array());
this.addSenderSignature({ pub: senderAuthenticator.public_key.toString() }, senderSignature);

const feePayerAuthenticator = authenticator.fee_payer.authenticator as AccountAuthenticatorEd25519;
const feePayerSignature = Buffer.from(feePayerAuthenticator.signature.toUint8Array());
this.addFeePayerSignature({ pub: feePayerAuthenticator.public_key.toString() }, feePayerSignature);
} catch (e) {
console.error('invalid signed transaction', e);
throw new Error('invalid signed transaction');
}
}

canSign(_key: BaseKey): boolean {
return false;
Expand Down Expand Up @@ -249,6 +277,21 @@ export abstract class Transaction extends BaseTransaction {
}
}

toJson(): TxData {
return {
id: this.id,
sender: this.sender,
recipient: this.recipient,
sequenceNumber: this.sequenceNumber,
maxGasAmount: this.maxGasAmount,
gasUnitPrice: this.gasUnitPrice,
gasUsed: this.gasUsed,
expirationTime: this.expirationTime,
feePayer: this.feePayerAddress,
assetId: this.assetId,
};
}

public getFee(): string {
return new BigNumber(this.gasUsed).multipliedBy(this.gasUnitPrice).toString();
}
Expand Down
66 changes: 11 additions & 55 deletions modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { Transaction } from './transaction';
import { TransferTxData } from '../iface';
import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { InvalidTransactionError, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import {
AccountAddress,
AccountAuthenticatorEd25519,
Aptos,
AptosConfig,
Network,
SignedTransaction,
TransactionAuthenticatorFeePayer,
TransactionPayload,
TransactionPayloadEntryFunction,
} from '@aptos-labs/ts-sdk';
import utils from '../utils';

import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics';

export class TransferTransaction extends Transaction {
Expand All @@ -21,57 +17,17 @@ export class TransferTransaction extends Transaction {
this._type = TransactionType.Send;
}

toJson(): TransferTxData {
return {
id: this.id,
sender: this.sender,
recipient: this.recipient,
sequenceNumber: this.sequenceNumber,
maxGasAmount: this.maxGasAmount,
gasUnitPrice: this.gasUnitPrice,
gasUsed: this.gasUsed,
expirationTime: this.expirationTime,
feePayer: this.feePayerAddress,
};
}

protected getRecipientFromTransactionPayload(payload: TransactionPayload): TransactionRecipient {
let address = 'INVALID';
let amount = '0';
if (payload instanceof TransactionPayloadEntryFunction) {
const entryFunction = payload.entryFunction;
address = entryFunction.args[0].toString();
const amountBuffer = Buffer.from(entryFunction.args[1].bcsToBytes());
amount = amountBuffer.readBigUint64LE().toString();
protected parseTransactionPayload(payload: TransactionPayload): void {
if (!(payload instanceof TransactionPayloadEntryFunction)) {
throw new InvalidTransactionError('Invalid transaction payload');
}
return { address, amount };
}

fromDeserializedSignedTransaction(signedTxn: SignedTransaction): void {
try {
const rawTxn = signedTxn.raw_txn;
this._sender = rawTxn.sender.toString();
this._recipient = this.getRecipientFromTransactionPayload(rawTxn.payload);
this._sequenceNumber = utils.castToNumber(rawTxn.sequence_number);
this._maxGasAmount = utils.castToNumber(rawTxn.max_gas_amount);
this._gasUnitPrice = utils.castToNumber(rawTxn.gas_unit_price);
this._expirationTime = utils.castToNumber(rawTxn.expiration_timestamp_secs);
this._rawTransaction = rawTxn;

this.loadInputsAndOutputs();
const authenticator = signedTxn.authenticator as TransactionAuthenticatorFeePayer;
this._feePayerAddress = authenticator.fee_payer.address.toString();
const senderAuthenticator = authenticator.sender as AccountAuthenticatorEd25519;
const senderSignature = Buffer.from(senderAuthenticator.signature.toUint8Array());
this.addSenderSignature({ pub: senderAuthenticator.public_key.toString() }, senderSignature);

const feePayerAuthenticator = authenticator.fee_payer.authenticator as AccountAuthenticatorEd25519;
const feePayerSignature = Buffer.from(feePayerAuthenticator.signature.toUint8Array());
this.addFeePayerSignature({ pub: feePayerAuthenticator.public_key.toString() }, feePayerSignature);
} catch (e) {
console.error('invalid signed transaction', e);
throw new Error('invalid signed transaction');
const entryFunction = payload.entryFunction;
if (!this._recipient) {
this._recipient = {} as TransactionRecipient;
}
this._recipient.address = entryFunction.args[0].toString();
const amountBuffer = Buffer.from(entryFunction.args[1].bcsToBytes());
this._recipient.amount = amountBuffer.readBigUint64LE().toString();
}

protected async buildRawTransaction(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { TransactionBuilder } from './transactionBuilder';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { FungibleAssetTransaction } from '../transaction/fungibleAssetTransaction';
import { TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { TransactionType } from '@bitgo/sdk-core';
import { Transaction } from '../transaction/transaction';
import BigNumber from 'bignumber.js';
import utils from '../utils';
import { TransactionPayload, TransactionPayloadEntryFunction } from '@aptos-labs/ts-sdk';

export class FungibleAssetTransactionBuilder extends TransactionBuilder {
//REMOVE COMMENT
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._transaction = new FungibleAssetTransaction(_coinConfig);
}

fungibleTokenAddress(fungibleTokenAddress: string): this {
this.validateAddress({ address: fungibleTokenAddress });
(this.transaction as FungibleAssetTransaction).fungibleTokenAddress = fungibleTokenAddress;
return this;
}

protected get transactionType(): TransactionType {
return TransactionType.SendToken;
}
Expand All @@ -37,38 +30,27 @@ export class FungibleAssetTransactionBuilder extends TransactionBuilder {
this.validateAddress({ address: transaction.sender });
this.validateAddress({ address: transaction.recipient.address });
this.validateValue(new BigNumber(transaction.recipient.amount));
this.validateAddress({ address: transaction.fungibleTokenAddress });
this.validateAddress({ address: transaction.assetId });
}

getRecipientFromTransactionPayload(payload: TransactionPayload): TransactionRecipient {
let address = 'INVALID';
let amount = '0';
if (payload instanceof TransactionPayloadEntryFunction) {
protected isValidTransactionPayload(payload: TransactionPayload) {
try {
if (!(payload instanceof TransactionPayloadEntryFunction) || payload.entryFunction.args.length !== 3) {
console.error('invalid transaction payload');
return false;
}
const entryFunction = payload.entryFunction;
address = entryFunction.args[1].toString();
const fungibleTokenAddress = entryFunction.args[0].toString();
const recipientAddress = entryFunction.args[1].toString();
const amountBuffer = Buffer.from(entryFunction.args[2].bcsToBytes());
amount = amountBuffer.readBigUint64LE().toString();
}
return { address, amount };
}

isValidRawTransaction(rawTransaction: string): boolean {
try {
const signedTxn = utils.deserializeSignedTransaction(rawTransaction);
const rawTxn = signedTxn.raw_txn;
const senderAddress = rawTxn.sender.toString();
const recipient = this.getRecipientFromTransactionPayload(rawTxn.payload);
const fungibleTokenAddress = utils.getFungibleTokenAddressFromTransactionPayload(rawTxn.payload);
const recipientAddress = recipient.address;
const recipientAmount = new BigNumber(recipient.amount);
const recipientAmount = new BigNumber(amountBuffer.readBigUint64LE().toString());
return (
utils.isValidAddress(senderAddress) &&
utils.isValidAddress(recipientAddress) &&
utils.isValidAddress(fungibleTokenAddress) &&
!recipientAmount.isLessThan(0)
);
} catch (e) {
console.error('invalid fungible raw transaction', e);
console.error('invalid transaction payload', e);
return false;
}
}
Expand Down
Loading

0 comments on commit 7e0ac97

Please sign in to comment.