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 f57e2bc
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 42 deletions.
18 changes: 18 additions & 0 deletions modules/sdk-coin-ton/src/lib/singleNominatorWithdrawBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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.StakingWithdraw;
}

send(recipient: Recipient): SingleNominatorWithdrawBuilder {
this.transaction.recipient = recipient;
return this;
}
}
76 changes: 65 additions & 11 deletions modules/sdk-coin-ton/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class Transaction extends BaseTransaction {
public bounceable: boolean;
public fromAddressBounceable: boolean;
public toAddressBounceable: boolean;
public message: string;
public message: string | Cell;
seqno: number;
expireTime: number;
sender: string;
Expand Down Expand Up @@ -67,8 +67,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 +125,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,7 +171,7 @@ 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);
Expand Down Expand Up @@ -198,7 +205,7 @@ export class Transaction extends BaseTransaction {
};
}

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

// header
Expand Down Expand Up @@ -239,11 +246,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,16 +295,28 @@ export class Transaction extends BaseTransaction {

// order body
let payload;

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();
const amount = order.loadCoins().toNumber();
payload = new TonWeb.boc.Cell();
payload.bits.writeUint(opcode, 32);
payload.bits.writeUint(queryId, 64);
payload.bits.writeCoins(amount);
this.transactionType = TransactionType.StakingWithdraw;
} else {
payload = '';
}
}
}
return {
Expand All @@ -312,6 +331,41 @@ export class Transaction extends BaseTransaction {
};
}

// private parseTransferPayload(order: any): any {
// let payload;
// 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) : '';
// }
//
// return payload;
// }
//
// private parseWithdrawPayload(order: any): any {
// let payload;
// if (order.loadBit()) {
// order = order.loadRef();
// }
//
// if (order.getFreeBits() > 32) {
// const opcode = order.loadUint(32).toNumber();
// const query_id = order.loadUint(64).toNumber();
// const amount = order.loadCoins().toNumber();
// console.log('test', opcode, query_id, amount);
// payload = new TonWeb.boc.Cell();
// payload.bits.writeUint(opcode, 32);
// payload.bits.writeUint(query_id, 64);
// payload.bits.writeCoins(amount);
// }
//
// return payload;
// }

private parseTransferStateInit(slice: any): any {
if (slice === null) return {};
slice.loadRef();
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(amount: string): TransactionBuilder {
const message = new TonWeb.boc.Cell();
message.bits.writeUint(WITHDRAW_OPCODE, 32);
message.bits.writeUint(0, 64);
message.bits.writeCoins(TonWeb.utils.toNano(amount));
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.StakingWithdraw:
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
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 f57e2bc

Please sign in to comment.