Skip to content

Commit

Permalink
feat(sdk-coin-ton): add single nominator withdraw txn (Ticket: SC-370)
Browse files Browse the repository at this point in the history
  • Loading branch information
quarterdill committed Sep 26, 2024
1 parent a10dac8 commit f89be52
Show file tree
Hide file tree
Showing 11 changed files with 634 additions and 186 deletions.
6 changes: 6 additions & 0 deletions modules/sdk-coin-ton/src/lib/iface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { TransactionFee } from '@bitgo/sdk-core';
import { ITransactionExplanation } from './transactionExplanation';

/**
* The transaction data returned from the toJson() function of a transaction
*/
Expand All @@ -7,9 +10,12 @@ export interface TxData {
destination: string;
destinationAlias: string;
amount: string;
withdrawAmount: string;
seqno: number;
expirationTime: number;
publicKey: string;
signature: string;
bounceable: boolean;
}

export type TransactionExplanation = ITransactionExplanation<TransactionFee>;
27 changes: 27 additions & 0 deletions modules/sdk-coin-ton/src/lib/singleNominatorWithdrawBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TransactionBuilder } from './transactionBuilder';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { Recipient, TransactionType } from '@bitgo/sdk-core';

export class SingleNominatorWithdrawBuilder extends TransactionBuilder {
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}

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

setWithdrawAmount(amount: string): SingleNominatorWithdrawBuilder {
this.transaction.withdrawAmount = amount;
return this;
}

send(recipient: Recipient): SingleNominatorWithdrawBuilder {
this.transaction.recipient = recipient;
return this;
}

setMessage(msg: string): SingleNominatorWithdrawBuilder {
throw new Error('Method not implemented.');
}
}
62 changes: 40 additions & 22 deletions modules/sdk-coin-ton/src/lib/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import {
BaseKey,
BaseTransaction,
Entry,
Recipient,
TransactionRecipient,
TransactionType,
TransactionExplanation,
} from '@bitgo/sdk-core';
import { TxData } from './iface';
import { BaseKey, BaseTransaction, Entry, Recipient, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { TxData, TransactionExplanation } from './iface';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import TonWeb from 'tonweb';
import { BN } from 'bn.js';
Expand All @@ -20,7 +12,8 @@ export class Transaction extends BaseTransaction {
public bounceable: boolean;
public fromAddressBounceable: boolean;
public toAddressBounceable: boolean;
public message: string;
public message: string | Cell;
public withdrawAmount: string;
seqno: number;
expireTime: number;
sender: string;
Expand Down Expand Up @@ -55,6 +48,7 @@ export class Transaction extends BaseTransaction {
destination: this.recipient.address,
destinationAlias: otherFormat,
amount: this.recipient.amount,
withdrawAmount: this.withdrawAmount,
seqno: this.seqno,
expirationTime: this.expireTime,
publicKey: this.publicKey,
Expand All @@ -67,8 +61,15 @@ export class Transaction extends BaseTransaction {
return Buffer.from(this.unsignedMessage, 'hex');
}

/**
* Set the transaction type.
* @param {TransactionType} transactionType The transaction type to be set.
*/
set transactionType(transactionType: TransactionType) {
this._type = transactionType;
}

async build(): Promise<void> {
this._type = TransactionType.Send;
const signingMessage = this.createSigningMessage(WALLET_ID, this.seqno, this.expireTime);
const sendMode = 3;
signingMessage.bits.writeUint8(sendMode);
Expand Down Expand Up @@ -118,7 +119,7 @@ export class Transaction extends BaseTransaction {
true,
this.bounceable
);
return TonWeb.Contract.createCommonMsgInfo(orderHeader, undefined, payloadCell);
return TonWeb.Contract.createCommonMsgInfo(orderHeader, undefined, payloadCell); // compare with commonmsg tonweb and ton
}

async createExternalMessage(signingMessage: Cell, seqno: number, signature: string): Promise<Cell> {
Expand Down Expand Up @@ -164,12 +165,13 @@ export class Transaction extends BaseTransaction {
try {
const cell = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(rawTransaction));

const parsed = this.parseTransfer(cell);
const parsed = this.parseTransaction(cell);
parsed.value = parsed.value.toString();
parsed.fromAddress = parsed.fromAddress.toString(true, true, this.fromAddressBounceable);
parsed.toAddress = parsed.toAddress.toString(true, true, this.toAddressBounceable);
this.sender = parsed.fromAddress;
this.recipient = { address: parsed.toAddress, amount: parsed.value };
this.withdrawAmount = parsed.withdrawAmount;
this.seqno = parsed.seqno;
this.publicKey = parsed.publicKey as string;
this.expireTime = parsed.expireAt;
Expand All @@ -183,10 +185,11 @@ export class Transaction extends BaseTransaction {

/** @inheritDoc */
explainTransaction(): TransactionExplanation {
const displayOrder = ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee'];
const displayOrder = ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'withdrawAmount'];

const outputs: TransactionRecipient[] = [this.recipient];
const outputAmount = this.recipient.amount;
const withdrawAmount = this.withdrawAmount;
return {
displayOrder,
id: this.id,
Expand All @@ -195,10 +198,11 @@ export class Transaction extends BaseTransaction {
changeOutputs: [],
changeAmount: '0',
fee: { fee: 'UNKNOWN' },
withdrawAmount,
};
}

private parseTransfer(cell: Cell): any {
private parseTransaction(cell: Cell): any {
const slice = (cell as any).beginParse();

// header
Expand Down Expand Up @@ -239,11 +243,11 @@ export class Transaction extends BaseTransaction {
return {
fromAddress: externalDestAddress,
publicKey,
...this.parseTransferBody(bodySlice),
...this.parseTransactionBody(bodySlice),
};
}

private parseTransferBody(slice: any): any {
private parseTransactionBody(slice: any): any {
const signature = Buffer.from(slice.loadBits(512)).toString('hex');
// signing message

Expand Down Expand Up @@ -288,23 +292,37 @@ export class Transaction extends BaseTransaction {

// order body
let payload;

let withdrawAmount;
this.transactionType = TransactionType.Send;
if (order.getFreeBits() > 0) {
if (order.loadBit()) {
order = order.loadRef();
}

if (order.getFreeBits() > 32) {
const op = order.loadUint(32);
const payloadBytes = order.loadBits(order.getFreeBits());
payload = op.eq(new BN(0)) ? new TextDecoder().decode(payloadBytes) : '';
const opcode = order.loadUint(32).toNumber();
if (opcode === 0) {
const payloadBytes = order.loadBits(order.getFreeBits());
payload = new TextDecoder().decode(payloadBytes);
} else if (opcode === 4096) {
const queryId = order.loadUint(64).toNumber();
withdrawAmount = (order.loadCoins().toNumber() / 1e9).toString();
payload = new TonWeb.boc.Cell();
payload.bits.writeUint(opcode, 32);
payload.bits.writeUint(queryId, 64);
payload.bits.writeCoins(TonWeb.utils.toNano(withdrawAmount));
this.transactionType = TransactionType.SingleNominatorWithdraw;
} else {
payload = '';
}
}
}
return {
toAddress: destAddress,
value,
bounce,
seqno,
withdrawAmount,
expireAt,
payload,
signature,
Expand Down
13 changes: 13 additions & 0 deletions modules/sdk-coin-ton/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import BigNumber from 'bignumber.js';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import TonWeb from 'tonweb';

const WITHDRAW_OPCODE = 4096;

export abstract class TransactionBuilder extends BaseTransactionBuilder {
protected _transaction: Transaction;
private _signatures: Signature[] = [];
Expand Down Expand Up @@ -106,12 +108,14 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {

/** @inheritdoc */
protected fromImplementation(rawTransaction: string): Transaction {
this.transaction.transactionType = this.transactionType;
this.transaction.fromRawTransaction(rawTransaction);
return this.transaction;
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
this.transaction.transactionType = this.transactionType;
await this.transaction.build();
this.transaction.loadInputsAndOutputs();
return this.transaction;
Expand Down Expand Up @@ -156,6 +160,15 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
return this;
}

setWithdrawMessage(): TransactionBuilder {
const message = new TonWeb.boc.Cell();
message.bits.writeUint(WITHDRAW_OPCODE, 32);
message.bits.writeUint(0, 64);
message.bits.writeCoins(TonWeb.utils.toNano(this.transaction.withdrawAmount));
this.transaction.message = message;
return this;
}

sequenceNumber(number: number): TransactionBuilder {
this.transaction.seqno = number;
return this;
Expand Down
33 changes: 29 additions & 4 deletions modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
import { BaseTransactionBuilderFactory } from '@bitgo/sdk-core';
import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
import { TransactionBuilder } from './transactionBuilder';
import { TransferBuilder } from './transferBuilder';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { SingleNominatorWithdrawBuilder } from './singleNominatorWithdrawBuilder';
import { Transaction } from './transaction';

export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
}
/** @inheritdoc */
from(raw: string): TransactionBuilder {
const builder = new TransferBuilder(this._coinConfig);
builder.from(raw);
return builder;
let builder: TransactionBuilder;
const tx = new Transaction(this._coinConfig);
tx.fromRawTransaction(raw);
try {
switch (tx.type) {
case TransactionType.Send:
builder = this.getTransferBuilder();
break;
case TransactionType.SingleNominatorWithdraw:
builder = this.getSingleNominatorWithdrawBuilder();
break;
default:
throw new InvalidTransactionError('unsupported transaction');
}
builder.from(raw);
return builder;
} catch (e) {
throw e;
}
}

/** @inheritdoc */
getTransferBuilder(): TransferBuilder {
return new TransferBuilder(this._coinConfig);
}

/**
* Returns a specific builder to create a TON withdraw transaction
*/
getSingleNominatorWithdrawBuilder(): SingleNominatorWithdrawBuilder {
return new SingleNominatorWithdrawBuilder(this._coinConfig);
}

/** @inheritdoc */
getWalletInitializationBuilder(): void {
throw new Error('Method not implemented.');
Expand Down
14 changes: 14 additions & 0 deletions modules/sdk-coin-ton/src/lib/transactionExplanation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ITransactionRecipient } from '@bitgo/sdk-core';

export interface ITransactionExplanation<TFee = any, TAmount = any> {
displayOrder: string[];
id: string;
outputs: ITransactionRecipient[];
outputAmount: TAmount;
changeOutputs: ITransactionRecipient[];
changeAmount: TAmount;
fee: TFee;
proxy?: string;
producers?: string[];
withdrawAmount?: string;
}
20 changes: 19 additions & 1 deletion modules/sdk-coin-ton/test/resources/ton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const recipients: Recipient[] = [
},
];

export const signedTransaction = {
export const signedSendTransaction = {
tx: 'te6cckEBAgEAqQAB4YgBJAxo7vqHF++LJ4bC/kJ8A1uVRskrKlrKJZ8rIB0tF+gCadlSX+hPo2mmhZyi0p3zTVUYVRkcmrCm97cSUFSa2vzvCArM3APg+ww92r3IcklNjnzfKOgysJVQXiCvj9SAaU1NGLsotvRwAAAAMAAcAQBmQgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr5zEtAAAAAAAAAAAAAAAAAAAdfZO7w==',
txBounceable:
'te6cckEBAgEAqQAB4YgBJAxo7vqHF++LJ4bC/kJ8A1uVRskrKlrKJZ8rIB0tF+gCadlSX+hPo2mmhZyi0p3zTVUYVRkcmrCm97cSUFSa2vzvCArM3APg+ww92r3IcklNjnzfKOgysJVQXiCvj9SAaU1NGLsotvRwAAAAMAAcAQBmYgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr5zEtAAAAAAAAAAAAAAAAAAAYubM0w==',
Expand All @@ -51,3 +51,21 @@ export const signedTransaction = {
amount: '10000000',
},
};

export const signedTransferTransaction = {
tx: 'te6cckEBAgEAugAB4YgANk3QfSfW3PrGRiBaolJGaRREphqOMSBd5rAGj1yJ88gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1NGLsotvRwAAAAMAAcAQCHQgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr5zEtAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAQ7msoAg2I8aq',
txBounceable:
'te6cckEBAgEAugAB4YgANk3QfSfW3PrGRiBaolJGaRREphqOMSBd5rAGj1yJ88gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1NGLsotvRwAAAAMAAcAQCHYgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr5zEtAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAQ7msoAhFP9Yn',
txId: 'GUD-auBCZ3PJFfAPkSfeqe8rj2OiHMTXudH4IEWdDgo=',
txIdBounceable: 'GUD-auBCZ3PJFfAPkSfeqe8rj2OiHMTXudH4IEWdDgo=',
signable: 'cX/GEZo6PX0rrw35kBsOk91u0CyWEzASSjM0yzfAHp4=',
bounceableSignable: 'cX/GEZo6PX0rrw35kBsOk91u0CyWEzASSjM0yzfAHp4=',
recipient: {
address: 'EQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBXwtG',
amount: '10000000',
},
recipientBounceable: {
address: 'UQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBX1aD',
amount: '10000000',
},
};
Loading

0 comments on commit f89be52

Please sign in to comment.