Skip to content

Commit 76f45da

Browse files
committed
feat(sdk-coin-apt): addition of test case for apt transaction builder
Ticket: COIN-2258
1 parent 6857221 commit 76f45da

File tree

10 files changed

+521
-41
lines changed

10 files changed

+521
-41
lines changed

modules/sdk-coin-apt/src/apt.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ import {
1111
VerifyAddressOptions,
1212
VerifyTransactionOptions,
1313
} from '@bitgo/sdk-core';
14-
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
15-
import { KeyPair as AptKeyPair } from './lib';
14+
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
15+
import { KeyPair as AptKeyPair, TransactionBuilderFactory, TransferTransaction } from './lib';
1616
import utils from './lib/utils';
17+
import * as _ from 'lodash';
18+
import BigNumber from 'bignumber.js';
19+
20+
export interface AptParseTransactionOptions extends ParseTransactionOptions {
21+
txHex: string;
22+
}
1723

1824
export class Apt extends BaseCoin {
1925
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -64,7 +70,40 @@ export class Apt extends BaseCoin {
6470
}
6571

6672
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
67-
throw new Error('Method not implemented.');
73+
const coinConfig = coins.get(this.getChain());
74+
const { txPrebuild: txPrebuild, txParams: txParams } = params;
75+
const transaction = new TransferTransaction(coinConfig);
76+
const rawTx = txPrebuild.txHex;
77+
if (!rawTx) {
78+
throw new Error('missing required tx prebuild property txHex');
79+
}
80+
transaction.fromRawTransaction(rawTx);
81+
const explainedTx = transaction.explainTransaction();
82+
if (txParams.recipients !== undefined) {
83+
const filteredRecipients = txParams.recipients?.map((recipient) => {
84+
return {
85+
address: recipient.address, // TODO: check this
86+
amount: BigInt(recipient.amount),
87+
};
88+
});
89+
const filteredOutputs = explainedTx.outputs.map((output) => {
90+
return {
91+
address: output.address,
92+
amount: BigInt(output.amount),
93+
};
94+
});
95+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
96+
throw new Error('Tx outputs does not match with expected txParams recipients');
97+
}
98+
let totalAmount = new BigNumber(0);
99+
for (const recipients of txParams.recipients) {
100+
totalAmount = totalAmount.plus(recipients.amount);
101+
}
102+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
103+
throw new Error('Tx total amount does not match with expected total amount field');
104+
}
105+
}
106+
return true;
68107
}
69108

70109
async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
@@ -76,8 +115,26 @@ export class Apt extends BaseCoin {
76115
return true;
77116
}
78117

79-
parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
80-
throw new Error('Method not implemented.');
118+
async parseTransaction(params: AptParseTransactionOptions): Promise<ParsedTransaction> {
119+
const coinConfig = coins.get(this.getChain());
120+
const factory = new TransactionBuilderFactory(coinConfig);
121+
const transactionBuilder = factory.from(params.txHex);
122+
const rebuiltTransaction = await transactionBuilder.build();
123+
const parsedTransaction = rebuiltTransaction.toJson();
124+
return {
125+
inputs: [
126+
{
127+
address: parsedTransaction.sender,
128+
value: parsedTransaction.recipient.amount,
129+
},
130+
],
131+
outputs: [
132+
{
133+
address: parsedTransaction.recipient.address,
134+
value: parsedTransaction.recipient.amount,
135+
},
136+
],
137+
};
81138
}
82139

83140
generateKeyPair(seed?: Buffer): KeyPair {

modules/sdk-coin-apt/src/lib/iface.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { ITransactionExplanation, TransactionFee } from '@bitgo/sdk-core';
22

3+
export enum AptTransactionType {
4+
Transfer = 'Transfer',
5+
TokenTransfer = 'TokenTransfer',
6+
}
37
export type TransactionExplanation = ITransactionExplanation<TransactionFee>;
48

59
/**

modules/sdk-coin-apt/src/lib/transaction/transaction.ts

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import {
1010
import { TransactionExplanation, TxData } from '../iface';
1111
import { BaseCoin as CoinConfig } from '@bitgo/statics';
1212
import {
13-
AccountAddress,
1413
AccountAuthenticatorEd25519,
1514
Ed25519PublicKey,
1615
Ed25519Signature,
1716
generateUserTransactionHash,
17+
Hex,
1818
RawTransaction,
1919
SignedTransaction,
2020
SimpleTransaction,
@@ -27,6 +27,13 @@ export abstract class Transaction extends BaseTransaction {
2727
protected _rawTransaction: RawTransaction;
2828
protected _signature: Signature;
2929

30+
protected _sender: string;
31+
protected _recipient: TransactionRecipient;
32+
protected _sequenceNumber: bigint;
33+
protected _maxGasAmount: bigint;
34+
protected _gasUnitPrice: bigint;
35+
protected _expirationTime: bigint;
36+
protected _chainId: number;
3037
static DEFAULT_PUBLIC_KEY = Buffer.alloc(32);
3138
static DEFAULT_SIGNATURE = Buffer.alloc(64);
3239

@@ -38,37 +45,69 @@ export abstract class Transaction extends BaseTransaction {
3845
public get id(): string {
3946
return this._id ?? UNAVAILABLE_TEXT;
4047
}
48+
get sender(): string {
49+
return this._sender;
50+
}
4151

42-
set transactionType(transactionType: TransactionType) {
43-
this._type = transactionType;
52+
set sender(value: string) {
53+
this._sender = value;
4454
}
4555

46-
public get signablePayload(): Buffer {
47-
const rawTxnHex = this._rawTransaction.bcsToHex().toString();
48-
return Buffer.from(rawTxnHex, 'hex');
56+
get recipient(): TransactionRecipient {
57+
return this._recipient;
58+
}
59+
60+
set recipient(value: TransactionRecipient) {
61+
this._recipient = value;
62+
}
63+
64+
get sequenceNumber(): bigint {
65+
return this._sequenceNumber;
66+
}
67+
68+
set sequenceNumber(value: bigint) {
69+
this._sequenceNumber = value;
70+
}
71+
72+
get maxGasAmount(): bigint {
73+
return this._maxGasAmount;
74+
}
75+
76+
set maxGasAmount(value: bigint) {
77+
this._maxGasAmount = value;
78+
}
79+
80+
get gasUnitPrice(): bigint {
81+
return this._gasUnitPrice;
82+
}
83+
84+
set gasUnitPrice(value: bigint) {
85+
this._gasUnitPrice = value;
86+
}
87+
88+
get expirationTime(): bigint {
89+
return this._expirationTime;
90+
}
91+
92+
set expirationTime(value: bigint) {
93+
this._expirationTime = value;
4994
}
5095

51-
public get sender(): string {
52-
return this._rawTransaction.sender.toString();
96+
get chainId(): number {
97+
return this._chainId;
5398
}
5499

55-
set sender(senderAddress: string) {
56-
// Cannot assign to 'sender' because it is a read-only property in RawTransaction.
57-
const { sequence_number, payload, max_gas_amount, gas_unit_price, expiration_timestamp_secs, chain_id } =
58-
this._rawTransaction;
59-
this._rawTransaction = new RawTransaction(
60-
AccountAddress.fromString(senderAddress),
61-
sequence_number,
62-
payload,
63-
max_gas_amount,
64-
gas_unit_price,
65-
expiration_timestamp_secs,
66-
chain_id
67-
);
100+
set chainId(value: number) {
101+
this._chainId = value;
68102
}
69103

70-
public get recipient(): TransactionRecipient {
71-
return utils.getRecipientFromTransactionPayload(this._rawTransaction.payload);
104+
set transactionType(transactionType: TransactionType) {
105+
this._type = transactionType;
106+
}
107+
108+
public get signablePayload(): Buffer {
109+
const rawTxnHex = this._rawTransaction.bcsToHex().toString();
110+
return Buffer.from(rawTxnHex, 'hex');
72111
}
73112

74113
canSign(_key: BaseKey): boolean {
@@ -81,17 +120,33 @@ export abstract class Transaction extends BaseTransaction {
81120
}
82121
return this.serialize();
83122
}
123+
//constructor(sender: AccountAddress, sequence_number: bigint, payload:
124+
// TransactionPayload, max_gas_amount: bigint, gas_unit_price: bigint,
125+
// expiration_timestamp_secs: bigint, chain_id: ChainId);
126+
127+
// private initializeRawTransaction(){
128+
// this._rawTransaction = new RawTransaction(
129+
// AccountAddress.from(this.sender),
130+
// this._sequenceNumber,
131+
// this._maxGasAmount,
132+
// this._gasUnitPrice,
133+
// this._expirationTime,
134+
// this._chainId
135+
// new TransactionPayload()
136+
// );
137+
// }
84138

85139
serialize(): string {
86140
let publicKeyBuffer = Transaction.DEFAULT_PUBLIC_KEY;
87141
let signatureBuffer = Transaction.DEFAULT_SIGNATURE;
88142
if (this._signature && this._signature.publicKey && this._signature.signature) {
89-
publicKeyBuffer = Buffer.from(this._signature.publicKey.pub, 'hex');
143+
publicKeyBuffer = Buffer.from(Hex.fromHexString(this._signature.publicKey.pub).toUint8Array());
90144
signatureBuffer = this._signature.signature;
91145
}
92146
const publicKey = new Ed25519PublicKey(publicKeyBuffer);
93147
const signature = new Ed25519Signature(signatureBuffer);
94148
const txnAuthenticator = new TransactionAuthenticatorEd25519(publicKey, signature);
149+
// this._rawTransaction = new RawTransaction(AccountAddress.from(this.sender),)
95150
const signedTxn = new SignedTransaction(this._rawTransaction, txnAuthenticator);
96151
return signedTxn.toString();
97152
}
@@ -103,7 +158,6 @@ export abstract class Transaction extends BaseTransaction {
103158
if (!Transaction.DEFAULT_PUBLIC_KEY.equals(publicKeyBuffer) && !Transaction.DEFAULT_SIGNATURE.equals(signature)) {
104159
this._signatures.push(signature.toString('hex'));
105160
this._signature = { publicKey, signature };
106-
this.serialize();
107161
}
108162
}
109163

@@ -119,34 +173,43 @@ export abstract class Transaction extends BaseTransaction {
119173
}
120174

121175
loadInputsAndOutputs(): void {
122-
const txRecipient = this.recipient;
123176
this._inputs = [
124177
{
125-
address: this.sender,
126-
value: txRecipient.amount as string,
178+
address: this._sender,
179+
value: this.recipient.amount as string,
127180
coin: this._coinConfig.name,
128181
},
129182
];
130183
this._outputs = [
131184
{
132-
address: txRecipient.address,
133-
value: txRecipient.amount as string,
185+
address: this.recipient.address,
186+
value: this.recipient.amount as string,
134187
coin: this._coinConfig.name,
135188
},
136189
];
137190
}
138191

192+
private populateTransactionData(): void {
193+
if (!this._rawTransaction) {
194+
throw new InvalidTransactionError('Empty transaction');
195+
}
196+
this._sender = this._rawTransaction.sender.toString();
197+
this._recipient = utils.getRecipientFromTransactionPayload(this._rawTransaction.payload);
198+
this._maxGasAmount = this._rawTransaction.max_gas_amount;
199+
this._gasUnitPrice = this._rawTransaction.gas_unit_price;
200+
this._expirationTime = this._rawTransaction.expiration_timestamp_secs;
201+
this._chainId = this._rawTransaction.chain_id.chainId;
202+
}
203+
139204
fromRawTransaction(rawTransaction: string): void {
140205
try {
141206
const signedTxn = utils.deserializeSignedTransaction(rawTransaction);
142207
this._rawTransaction = signedTxn.raw_txn;
143-
208+
this.populateTransactionData();
144209
this.loadInputsAndOutputs();
145-
146210
const authenticator = signedTxn.authenticator as TransactionAuthenticatorEd25519;
147-
const publicKey = Buffer.from(authenticator.public_key.toUint8Array());
148211
const signature = Buffer.from(authenticator.signature.toUint8Array());
149-
this.addSignature({ pub: publicKey.toString() }, signature);
212+
this.addSignature({ pub: authenticator.public_key.toString() }, signature);
150213
} catch (e) {
151214
console.error('invalid raw transaction', e);
152215
throw new Error('invalid raw transaction');
@@ -155,6 +218,7 @@ export abstract class Transaction extends BaseTransaction {
155218

156219
/** @inheritDoc */
157220
explainTransaction(): TransactionExplanation {
221+
//TODO: explain transaction to take input as params then take build txn and return the explanation
158222
const displayOrder = ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'withdrawAmount'];
159223

160224
const outputs: TransactionRecipient[] = [this.recipient];

modules/sdk-coin-apt/src/lib/transaction/transferTransaction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { TxData } from '../iface';
33
import { TransactionPayloadEntryFunction } from '@aptos-labs/ts-sdk';
44

55
export class TransferTransaction extends Transaction {
6+
constructor(coinConfig) {
7+
super(coinConfig);
8+
}
69
toJson(): TxData {
710
const rawTxn = this._rawTransaction;
811
const payload = this._rawTransaction.payload as TransactionPayloadEntryFunction;

modules/sdk-coin-apt/src/lib/transferBuilder.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { TransactionBuilder } from './transactionBuilder';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3-
import { TransactionType } from '@bitgo/sdk-core';
3+
import { Recipient, TransactionType } from '@bitgo/sdk-core';
44
import { TransferTransaction } from './transaction/transferTransaction';
55

66
export class TransferBuilder extends TransactionBuilder {
77
constructor(_coinConfig: Readonly<CoinConfig>) {
88
super(_coinConfig);
9+
this.transaction = new TransferTransaction(_coinConfig);
910
}
1011

1112
protected get transactionType(): TransactionType {
1213
return TransactionType.Send;
1314
}
1415

16+
send(txRecipient: Recipient): this {
17+
this.transaction.txRecipient = txRecipient;
18+
return this;
19+
}
20+
1521
/**
1622
* Initialize the transaction builder fields using the decoded transaction data
1723
*

modules/sdk-coin-apt/src/lib/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export class Utils implements BaseUtils {
6767
if (payload instanceof TransactionPayloadEntryFunction) {
6868
const entryFunction = payload.entryFunction;
6969
address = entryFunction.args[0].toString();
70-
amount = entryFunction.args[1].toString();
70+
const amountBuffer = Buffer.from(entryFunction.args[1].bcsToBytes());
71+
amount = amountBuffer.readBigUint64LE().toString();
7172
}
7273
return { address, amount };
7374
}
@@ -107,6 +108,10 @@ export class Utils implements BaseUtils {
107108
const deserializer = new Deserializer(txnBytes);
108109
return deserializer.deserialize(SignedTransaction);
109110
}
111+
112+
// initializeRawTransaction(sender: string, sequenceNumber): RawTransaction {
113+
// return new RawTransaction();
114+
// }
110115
}
111116

112117
const utils = new Utils();

0 commit comments

Comments
 (0)