Skip to content

Commit e65d4eb

Browse files
Merge pull request #6408 from BitGo/COIN-4716-address-init-builder-vet
Coin 4716 address init builder vet
2 parents a152f42 + 09f5b21 commit e65d4eb

File tree

12 files changed

+584
-58
lines changed

12 files changed

+584
-58
lines changed

modules/sdk-coin-vet/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"bignumber.js": "^9.1.1",
5050
"ethereumjs-util": "7.1.5",
5151
"lodash": "^4.17.21",
52-
"tweetnacl": "^1.0.3"
52+
"tweetnacl": "^1.0.3",
53+
"ethereumjs-abi": "^0.6.5"
5354
},
5455
"devDependencies": {
5556
"@bitgo/sdk-api": "^1.64.3",

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
import {
22
TransactionExplanation as BaseTransactionExplanation,
33
TransactionType as BitGoTransactionType,
4+
TransactionRecipient,
45
} from '@bitgo/sdk-core';
56

67
/**
78
* The transaction data returned from the toJson() function of a transaction
89
*/
9-
export interface TxData {
10+
export interface VetTransactionData {
1011
id: string;
12+
chainTag: number;
13+
blockRef: string;
14+
expiration: number;
15+
gasPriceCoef: number;
16+
gas: number;
17+
dependsOn: string | null;
18+
nonce: number;
19+
sender?: string;
20+
feePayer?: string;
21+
recipients?: TransactionRecipient[];
22+
data?: string;
23+
value?: string;
24+
deployedAddress?: string;
25+
to?: string;
1126
}
1227

1328
export interface VetTransactionExplanation extends BaseTransactionExplanation {
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { TransactionType, InvalidTransactionError } from '@bitgo/sdk-core';
2+
import { getCreateForwarderParamsAndTypes, calculateForwarderV1Address } from '@bitgo/abstract-eth';
3+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
4+
import * as ethUtil from 'ethereumjs-util';
5+
import EthereumAbi from 'ethereumjs-abi';
6+
import { Transaction as VetTransaction, Secp256k1 } from '@vechain/sdk-core';
7+
import { Transaction } from './transaction';
8+
import { VetTransactionData } from '../iface';
9+
10+
export class AddressInitializationTransaction extends Transaction {
11+
private _baseAddress: string;
12+
private _feeAddress: string;
13+
private _salt: string;
14+
private _initCode: string;
15+
private _transactionData: string;
16+
private _deployedAddress: string;
17+
18+
constructor(_coinConfig: Readonly<CoinConfig>) {
19+
super(_coinConfig);
20+
this._type = TransactionType.AddressInitialization;
21+
}
22+
23+
get baseAddress(): string {
24+
return this._baseAddress;
25+
}
26+
27+
set baseAddress(address: string) {
28+
this._baseAddress = address;
29+
}
30+
31+
get feeAddress(): string {
32+
return this._feeAddress;
33+
}
34+
35+
set feeAddress(address: string) {
36+
this._feeAddress = address;
37+
}
38+
39+
get salt(): string {
40+
return this._salt;
41+
}
42+
43+
set salt(salt: string) {
44+
this._salt = salt;
45+
}
46+
47+
get initCode(): string {
48+
return this._initCode;
49+
}
50+
51+
set initCode(initCode: string) {
52+
this._initCode = initCode;
53+
}
54+
55+
get transactionData(): string {
56+
return this._transactionData;
57+
}
58+
59+
set transactionData(transactionData: string) {
60+
this._transactionData = transactionData;
61+
}
62+
63+
get deployedAddress(): string {
64+
return this._deployedAddress;
65+
}
66+
67+
set deployedAddress(address: string) {
68+
this._deployedAddress = address;
69+
}
70+
71+
/** @inheritdoc */
72+
async build(): Promise<void> {
73+
super.build();
74+
75+
if (this._salt && this._initCode) {
76+
const saltBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(this._salt), 32);
77+
78+
const { createForwarderParams, createForwarderTypes } = getCreateForwarderParamsAndTypes(
79+
this._baseAddress,
80+
saltBuffer,
81+
this._feeAddress
82+
);
83+
84+
// Hash the wallet base address and fee address if present with the given salt, so the address directly relies on the base address and fee address
85+
const calculationSalt = ethUtil.bufferToHex(
86+
EthereumAbi.soliditySHA3(createForwarderTypes, createForwarderParams)
87+
);
88+
this.deployedAddress = calculateForwarderV1Address(this._contract, calculationSalt, this._initCode);
89+
}
90+
}
91+
92+
/** @inheritdoc */
93+
buildClauses(): void {
94+
this._clauses = [
95+
{
96+
to: this._contract,
97+
value: '0x0',
98+
data: this._transactionData,
99+
},
100+
];
101+
}
102+
103+
/** @inheritdoc */
104+
toJson(): VetTransactionData {
105+
const json: VetTransactionData = {
106+
id: this.id,
107+
chainTag: this.chainTag,
108+
blockRef: this.blockRef,
109+
expiration: this.expiration,
110+
gasPriceCoef: this.gasPriceCoef,
111+
gas: this.gas,
112+
dependsOn: this.dependsOn,
113+
nonce: this.nonce,
114+
data: this.transactionData,
115+
value: '0',
116+
sender: this.sender,
117+
to: this.contract,
118+
deployedAddress: this.deployedAddress,
119+
};
120+
return json;
121+
}
122+
123+
/** @inheritdoc */
124+
fromDeserializedSignedTransaction(signedTx: VetTransaction): void {
125+
try {
126+
if (!signedTx || !signedTx.body) {
127+
throw new InvalidTransactionError('Invalid transaction: missing transaction body');
128+
}
129+
130+
// Store the raw transaction
131+
this.rawTransaction = signedTx;
132+
133+
// Set transaction body properties
134+
const body = signedTx.body;
135+
this.chainTag = typeof body.chainTag === 'number' ? body.chainTag : 0;
136+
this.blockRef = body.blockRef || '0x0';
137+
this.expiration = typeof body.expiration === 'number' ? body.expiration : 64;
138+
this.clauses = body.clauses || [];
139+
this.gasPriceCoef = typeof body.gasPriceCoef === 'number' ? body.gasPriceCoef : 128;
140+
this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0;
141+
this.dependsOn = body.dependsOn || null;
142+
this.nonce = typeof body.nonce === 'number' ? body.nonce : Number(body.nonce) || 0;
143+
// Set recipients from clauses
144+
this.contract = body.clauses[0]?.to || '0x0';
145+
this.transactionData = body.clauses[0]?.data || '0x0';
146+
147+
// Set sender address
148+
if (signedTx.origin) {
149+
this.sender = signedTx.origin.toString().toLowerCase();
150+
}
151+
152+
// Set signatures if present
153+
if (signedTx.signature) {
154+
// First signature is sender's signature
155+
this.senderSignature = Buffer.from(signedTx.signature.slice(0, Secp256k1.SIGNATURE_LENGTH));
156+
157+
// If there's additional signature data, it's the fee payer's signature
158+
if (signedTx.signature.length > Secp256k1.SIGNATURE_LENGTH) {
159+
this.feePayerSignature = Buffer.from(signedTx.signature.slice(Secp256k1.SIGNATURE_LENGTH));
160+
}
161+
}
162+
} catch (e) {
163+
throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`);
164+
}
165+
}
166+
}

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

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,19 @@ import {
1616
} from '@vechain/sdk-core';
1717
import * as nc_utils from '@noble/curves/abstract/utils';
1818
import utils from '../utils';
19-
import { VetTransactionExplanation } from '../iface';
20-
21-
export interface VetTransactionData {
22-
id: string;
23-
chainTag: number;
24-
blockRef: string;
25-
expiration: number;
26-
gasPriceCoef: number;
27-
gas: number;
28-
dependsOn: string | null;
29-
nonce: number;
30-
sender: string;
31-
feePayer: string;
32-
recipients: TransactionRecipient[];
33-
}
19+
import { VetTransactionExplanation, VetTransactionData } from '../iface';
3420

3521
const gasPrice = 1e13;
3622

3723
export class Transaction extends BaseTransaction {
3824
protected _rawTransaction: VetTransaction;
3925
protected _type: TransactionType;
4026
protected _recipients: TransactionRecipient[];
27+
protected _clauses: TransactionClause[];
28+
protected _contract: string;
4129
private _chainTag: number;
4230
private _blockRef: string;
4331
private _expiration: number;
44-
private _clauses: TransactionClause[];
4532
private _gasPriceCoef: number;
4633
private _gas: number;
4734
private _dependsOn: string | null;
@@ -207,6 +194,14 @@ export class Transaction extends BaseTransaction {
207194
this._feePayerPubKey = pubKey;
208195
}
209196

197+
get contract(): string {
198+
return this._contract;
199+
}
200+
201+
set contract(address: string) {
202+
this._contract = address;
203+
}
204+
210205
/**
211206
* Get all signatures associated with this transaction
212207
* Required by BaseTransaction
@@ -337,9 +332,9 @@ export class Transaction extends BaseTransaction {
337332

338333
/**
339334
* Sets the transaction ID from the raw transaction if it is signed
340-
* @private
335+
* @protected
341336
*/
342-
private generateTxnId(): void {
337+
protected generateTxnId(): void {
343338
// Check if we have a raw transaction
344339
if (!this.rawTransaction) {
345340
return;
@@ -382,33 +377,46 @@ export class Transaction extends BaseTransaction {
382377
gas: this.gas,
383378
dependsOn: null,
384379
nonce: this.nonce,
385-
reserved: {
386-
features: 1, // mark transaction as delegated i.e. will use gas payer
387-
},
388380
};
389381

382+
if (this.type === TransactionType.Send) {
383+
transactionBody.reserved = {
384+
features: 1, // mark transaction as delegated i.e. will use gas payer
385+
};
386+
}
387+
390388
this.rawTransaction = VetTransaction.of(transactionBody);
391389
}
392390

393391
loadInputsAndOutputs(): void {
394-
const totalAmount = this._recipients.reduce(
395-
(accumulator, current) => accumulator.plus(current.amount),
396-
new BigNumber('0')
397-
);
398-
this._inputs = [
399-
{
400-
address: this.sender,
401-
value: totalAmount.toString(),
402-
coin: this._coinConfig.name,
403-
},
404-
];
405-
this._outputs = this._recipients.map((recipient) => {
406-
return {
407-
address: recipient.address,
408-
value: recipient.amount as string,
409-
coin: this._coinConfig.name,
410-
};
411-
});
392+
switch (this.type) {
393+
case TransactionType.AddressInitialization:
394+
this._type = TransactionType.AddressInitialization;
395+
break;
396+
case TransactionType.Send:
397+
this._type = TransactionType.Send;
398+
const totalAmount = this._recipients.reduce(
399+
(accumulator, current) => accumulator.plus(current.amount),
400+
new BigNumber('0')
401+
);
402+
this._inputs = [
403+
{
404+
address: this.sender,
405+
value: totalAmount.toString(),
406+
coin: this._coinConfig.name,
407+
},
408+
];
409+
this._outputs = this._recipients.map((recipient) => {
410+
return {
411+
address: recipient.address,
412+
value: recipient.amount as string,
413+
coin: this._coinConfig.name,
414+
};
415+
});
416+
break;
417+
default:
418+
throw new InvalidTransactionError(`Unsupported transaction type: ${this.type}`);
419+
}
412420
}
413421

414422
fromRawTransaction(rawTransaction: string): void {

0 commit comments

Comments
 (0)