Skip to content

Commit 76dd8c7

Browse files
Merge pull request #6207 from BitGo/COIN-4109-add-undelegate-txBuilder
Coin 4109 add undelegate tx builder
2 parents bd4569b + 2e270bd commit 76dd8c7

File tree

7 files changed

+530
-1
lines changed

7 files changed

+530
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const DELEGATION_TYPE_URL = 'type.googleapis.com/protocol.DelegateResourceContract';
2+
export const UNDELEGATION_TYPE_URL = 'type.googleapis.com/protocol.UnDelegateResourceContract';

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ export class Transaction extends BaseTransaction {
201201
value: delegateValue.balance.toString(),
202202
};
203203
break;
204+
case ContractType.UnDelegateResourceContract:
205+
this._type = TransactionType.UnDelegateResource;
206+
const undelegateValue = (rawData.contract[0] as ResourceManagementContract).parameter.value;
207+
output = {
208+
address: undelegateValue.receiver_address,
209+
value: undelegateValue.balance.toString(),
210+
};
211+
input = {
212+
address: undelegateValue.owner_address,
213+
value: undelegateValue.balance.toString(),
214+
};
215+
break;
204216
default:
205217
throw new ParseTransactionError('Unsupported contract type');
206218
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { createHash } from 'crypto';
2+
import { TransactionType } from '@bitgo/sdk-core';
3+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
4+
5+
import { Transaction } from './transaction';
6+
import { TransactionReceipt, ResourceManagementContract } from './iface';
7+
import { UNDELEGATION_TYPE_URL } from './constants';
8+
import { decodeTransaction, getByteArrayFromHexAddress, TRANSACTION_DEFAULT_EXPIRATION } from './utils';
9+
import { protocol } from '../../resources/protobuf/tron';
10+
import { ResourceManagementTxBuilder } from './resourceManagementTxBuilder';
11+
12+
import ContractType = protocol.Transaction.Contract.ContractType;
13+
14+
export class UndelegateResourceTxBuilder extends ResourceManagementTxBuilder {
15+
constructor(_coinConfig: Readonly<CoinConfig>) {
16+
super(_coinConfig);
17+
}
18+
19+
/** @inheritdoc */
20+
protected get transactionType(): TransactionType {
21+
return TransactionType.UnDelegateResource;
22+
}
23+
24+
/**
25+
* Initialize the transaction builder fields using the transaction data
26+
*
27+
* @param {TransactionReceipt | string} rawTransaction the transaction data in a string or JSON format
28+
* @returns {DelegateResourceTxBuilder} the builder with the transaction data set
29+
*/
30+
initBuilder(rawTransaction: TransactionReceipt | string): this {
31+
this.validateRawTransaction(rawTransaction);
32+
const tx = this.fromImplementation(rawTransaction);
33+
this.transaction = tx;
34+
this._signingKeys = [];
35+
const rawData = tx.toJson().raw_data;
36+
this._refBlockBytes = rawData.ref_block_bytes;
37+
this._refBlockHash = rawData.ref_block_hash;
38+
this._expiration = rawData.expiration;
39+
this._timestamp = rawData.timestamp;
40+
this.transaction.setTransactionType(TransactionType.UnDelegateResource);
41+
const contractCall = rawData.contract[0] as ResourceManagementContract;
42+
this.initResourceManagementContractCall(contractCall);
43+
return this;
44+
}
45+
46+
/**
47+
* Helper method to create the undelegate resource transaction
48+
*/
49+
protected createResourceManagementTransaction(): void {
50+
const rawDataHex = this.getResourceManagementTxRawDataHex();
51+
const rawData = decodeTransaction(rawDataHex);
52+
const contract = rawData.contract[0] as ResourceManagementContract;
53+
const contractParameter = contract.parameter;
54+
contractParameter.value.owner_address = this._ownerAddress.toLocaleLowerCase();
55+
contractParameter.value.balance = Number(this._balance);
56+
contractParameter.value.receiver_address = this._receiverAddress.toLocaleLowerCase();
57+
contractParameter.value.resource = this._resource;
58+
contractParameter.type_url = UNDELEGATION_TYPE_URL;
59+
contract.type = 'UnDelegateResourceContract';
60+
const hexBuffer = Buffer.from(rawDataHex, 'hex');
61+
const id = createHash('sha256').update(hexBuffer).digest('hex');
62+
const txReceipt: TransactionReceipt = {
63+
raw_data: rawData,
64+
raw_data_hex: rawDataHex,
65+
txID: id,
66+
signature: this.transaction.signature,
67+
};
68+
this.transaction = new Transaction(this._coinConfig, txReceipt);
69+
}
70+
71+
/**
72+
* Helper method to get the undelegate resource transaction raw data hex
73+
*
74+
* @returns {string} the undelegate resource transaction raw data hex
75+
*/
76+
protected getResourceManagementTxRawDataHex(): string {
77+
const rawContract = {
78+
ownerAddress: getByteArrayFromHexAddress(this._ownerAddress),
79+
receiverAddress: getByteArrayFromHexAddress(this._receiverAddress),
80+
balance: this._balance,
81+
resource: this._resource,
82+
};
83+
const undelegateResourceContract = protocol.UnDelegateResourceContract.fromObject(rawContract);
84+
const undelegateResourceContractBytes =
85+
protocol.UnDelegateResourceContract.encode(undelegateResourceContract).finish();
86+
const txContract = {
87+
type: ContractType.UnDelegateResourceContract,
88+
parameter: {
89+
value: undelegateResourceContractBytes,
90+
type_url: UNDELEGATION_TYPE_URL,
91+
},
92+
};
93+
const raw = {
94+
refBlockBytes: Buffer.from(this._refBlockBytes, 'hex'),
95+
refBlockHash: Buffer.from(this._refBlockHash, 'hex'),
96+
expiration: this._expiration || Date.now() + TRANSACTION_DEFAULT_EXPIRATION,
97+
timestamp: this._timestamp || Date.now(),
98+
contract: [txContract],
99+
};
100+
const rawTx = protocol.Transaction.raw.create(raw);
101+
return Buffer.from(protocol.Transaction.raw.encode(rawTx).finish()).toString('hex');
102+
}
103+
}

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ export function decodeTransaction(hexString: string): RawData {
225225
contractType = ContractType.DelegateResourceContract;
226226
contract = decodeDelegateResourceContract(rawTransaction.contracts[0].parameter.value);
227227
break;
228+
case 'type.googleapis.com/protocol.UnDelegateResourceContract':
229+
contractType = ContractType.UnDelegateResourceContract;
230+
contract = decodeUnDelegateResourceContract(rawTransaction.contracts[0].parameter.value);
231+
break;
228232
default:
229233
throw new UtilsError('Unsupported contract type');
230234
}
@@ -598,7 +602,7 @@ export function decodeWithdrawExpireUnfreezeContract(base64: string): WithdrawEx
598602
* Deserialize the segment of the txHex corresponding with delegate resource contract
599603
*
600604
* @param {string} base64 - The base64 encoded contract data
601-
* @returns {DelegateResourceContractParameter[]} - Array containing the decoded delegate resource contract
605+
* @returns {ResourceManagementContractParameter[]} - Array containing the decoded delegate resource contract
602606
*/
603607
export function decodeDelegateResourceContract(base64: string): ResourceManagementContractParameter[] {
604608
let delegateResourceContract: ResourceManagementContractDecoded;
@@ -648,6 +652,60 @@ export function decodeDelegateResourceContract(base64: string): ResourceManageme
648652
];
649653
}
650654

655+
/**
656+
* Deserialize the segment of the txHex corresponding with undelegate resource contract
657+
*
658+
* @param {string} base64 - The base64 encoded contract data
659+
* @returns {ResourceManagementContractParameter[]} - Array containing the decoded undelegate resource contract
660+
*/
661+
export function decodeUnDelegateResourceContract(base64: string): ResourceManagementContractParameter[] {
662+
let undelegateResourceContract: ResourceManagementContractDecoded;
663+
try {
664+
undelegateResourceContract = protocol.UnDelegateResourceContract.decode(Buffer.from(base64, 'base64')).toJSON();
665+
} catch (e) {
666+
throw new UtilsError('There was an error decoding the delegate resource contract in the transaction.');
667+
}
668+
669+
if (!undelegateResourceContract.ownerAddress) {
670+
throw new UtilsError('Owner address does not exist in this delegate resource contract.');
671+
}
672+
673+
if (!undelegateResourceContract.receiverAddress) {
674+
throw new UtilsError('Receiver address does not exist in this delegate resource contract.');
675+
}
676+
677+
if (undelegateResourceContract.resource === undefined) {
678+
throw new UtilsError('Resource type does not exist in this delegate resource contract.');
679+
}
680+
681+
if (undelegateResourceContract.balance === undefined) {
682+
throw new UtilsError('Balance does not exist in this delegate resource contract.');
683+
}
684+
685+
const owner_address = getBase58AddressFromByteArray(
686+
getByteArrayFromHexAddress(Buffer.from(undelegateResourceContract.ownerAddress, 'base64').toString('hex'))
687+
);
688+
689+
const receiver_address = getBase58AddressFromByteArray(
690+
getByteArrayFromHexAddress(Buffer.from(undelegateResourceContract.receiverAddress, 'base64').toString('hex'))
691+
);
692+
693+
const resourceValue = !undelegateResourceContract.resource ? TronResource.BANDWIDTH : TronResource.ENERGY;
694+
695+
return [
696+
{
697+
parameter: {
698+
value: {
699+
resource: resourceValue,
700+
balance: Number(undelegateResourceContract.balance),
701+
owner_address,
702+
receiver_address,
703+
},
704+
},
705+
},
706+
];
707+
}
708+
651709
/**
652710
* @param raw
653711
*/

modules/sdk-coin-trx/src/lib/wrappedBuilder.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { VoteWitnessTxBuilder } from './voteWitnessTxBuilder';
1515
import { UnfreezeBalanceTxBuilder } from './unfreezeBalanceTxBuilder';
1616
import { WithdrawExpireUnfreezeTxBuilder } from './withdrawExpireUnfreezeTxBuilder';
1717
import { DelegateResourceTxBuilder } from './delegateResourceTxBuilder';
18+
import { UndelegateResourceTxBuilder } from './undelegateResourceTxBuilder';
1819

1920
/**
2021
* Wrapped Builder class
@@ -98,6 +99,16 @@ export class WrappedBuilder extends TransactionBuilder {
9899
return this.initializeBuilder(tx, new DelegateResourceTxBuilder(this._coinConfig));
99100
}
100101

102+
/**
103+
* Returns a specific builder to create a undelegate resource transaction
104+
*
105+
* @param {TransactionReceipt | string} [tx] The transaction to initialize builder
106+
* @returns {UndelegateResourceTxBuilder} The specific delegate resource builder
107+
*/
108+
getUnDelegateResourceTxBuilder(tx?: TransactionReceipt | string): UndelegateResourceTxBuilder {
109+
return this.initializeBuilder(tx, new UndelegateResourceTxBuilder(this._coinConfig));
110+
}
111+
101112
private initializeBuilder<T extends TransactionBuilder>(tx: TransactionReceipt | string | undefined, builder: T): T {
102113
if (tx) {
103114
builder.initBuilder(tx);
@@ -143,6 +154,8 @@ export class WrappedBuilder extends TransactionBuilder {
143154
return this.getWithdrawExpireUnfreezeTxBuilder(raw);
144155
case ContractType.DelegateResourceContract:
145156
return this.getDelegateResourceTxBuilder(raw);
157+
case ContractType.UnDelegateResourceContract:
158+
return this.getUnDelegateResourceTxBuilder(raw);
146159
default:
147160
throw new InvalidTransactionError('Invalid transaction type: ' + contractType);
148161
}

modules/sdk-coin-trx/test/resources.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const RESOURCE_ENERGY = 'ENERGY';
1414
export const RESOURCE_BANDWIDTH = 'BANDWIDTH';
1515
export const FROZEN_BALANCE = '1000000';
1616
export const DELEGATION_BALANCE = '1000000';
17+
export const UNDELEGATION_BALANCE = '1000000';
1718
export const UNFROZEN_BALANCE = '1000000';
1819
export const USDT_CONTRACT_ADDRESS = 'TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs';
1920
export const TOKEN_TRANSFER_RECIPIENT = 'TGai5uHgBcoLERrzDXMepqZB8Et7D8nV8K';
@@ -171,6 +172,21 @@ export const DELEGATE_RESOURCE_CONTRACT = [
171172
},
172173
];
173174

175+
export const UNDELEGATE_RESOURCE_CONTRACT = [
176+
{
177+
parameter: {
178+
value: {
179+
resource: 'ENERGY',
180+
balance: 1000000,
181+
owner_address: '41d6cd6a2c0ff35a319e6abb5b9503ba0278679882',
182+
receiver_address: '416ffedf93921506c3efdb510f7c4f256036c48a6a',
183+
},
184+
type_url: 'type.googleapis.com/protocol.UnDelegateResourceContract',
185+
},
186+
type: 'UnDelegateResourceContract',
187+
},
188+
];
189+
174190
// DO NOT RE-USE THIS PRV FOR REAL MONEY
175191
export const FirstPrivateKey = '2DBEAC1C22849F47514445A56AEF2EF164528A502DE4BD289E23EA1E2D4C4B06';
176192
export const SecondPrivateKey = 'FB3AA887E0BE3FAC9D75E661DAFF4A7FE0E91AAB13DA9775CD8586D7CB9B7640';

0 commit comments

Comments
 (0)