Skip to content

Commit 0300df5

Browse files
committed
feat(sdk-coin-near): added delegate transaction builder
Ticket: COIN-4149
1 parent 6270567 commit 0300df5

File tree

12 files changed

+936
-42
lines changed

12 files changed

+936
-42
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import assert from 'assert';
2+
import BigNumber from 'bignumber.js';
3+
4+
import * as hex from '@stablelib/hex';
5+
import * as nearAPI from 'near-api-js';
6+
import { DelegateAction } from '@near-js/transactions';
7+
8+
import {
9+
BaseAddress,
10+
BaseKey,
11+
BaseTransactionBuilder,
12+
BuildTransactionError,
13+
PublicKey as BasePublicKey,
14+
Signature,
15+
} from '@bitgo/sdk-core';
16+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
17+
18+
import { AddressValidationError } from './errors';
19+
import { BLOCK_HEIGHT_TTL } from './constants';
20+
import { DelegateTransaction } from './delegateTransaction';
21+
import { InitializableBuilder } from './initializableBuilder';
22+
import { KeyPair } from './keyPair';
23+
import utils from './utils';
24+
25+
export abstract class AbstractDelegateBuilder extends BaseTransactionBuilder implements InitializableBuilder {
26+
private _delegateTransaction: DelegateTransaction;
27+
28+
private _sender: string;
29+
private _publicKey: string;
30+
protected _receiverId: string;
31+
private _nonce: bigint;
32+
private _recentBlockHeight: bigint;
33+
private _signer: KeyPair;
34+
private _signatures: Signature[] = []; // only support single sig for now
35+
protected _actions: nearAPI.transactions.Action[];
36+
37+
constructor(_coinConfig: Readonly<CoinConfig>) {
38+
super(_coinConfig);
39+
this._delegateTransaction = new DelegateTransaction(_coinConfig);
40+
}
41+
42+
/**
43+
* Initialize the transaction builder fields using the decoded transaction data
44+
*
45+
* @param {Transaction} tx the transaction data
46+
*/
47+
initBuilder(tx: DelegateTransaction): void {
48+
this._delegateTransaction = tx;
49+
const nearDelegateAction = tx.nearTransaction;
50+
this._sender = nearDelegateAction.senderId;
51+
this._nonce = nearDelegateAction.nonce;
52+
this._receiverId = nearDelegateAction.receiverId;
53+
if (nearDelegateAction.publicKey.ed25519Key?.data) {
54+
this._publicKey = hex.encode(nearDelegateAction.publicKey.ed25519Key.data);
55+
}
56+
this._recentBlockHeight = nearDelegateAction.maxBlockHeight;
57+
this._actions = nearDelegateAction.actions;
58+
}
59+
60+
/** @inheritdoc */
61+
protected fromImplementation(rawTransaction: string): DelegateTransaction {
62+
this.validateRawTransaction(rawTransaction);
63+
this.buildImplementation();
64+
return this.transaction;
65+
}
66+
67+
/** @inheritdoc */
68+
protected async buildImplementation(): Promise<DelegateTransaction> {
69+
this.transaction.nearTransaction = this.buildNearTransaction();
70+
if (this._signer) {
71+
this.transaction.sign(this._signer);
72+
}
73+
if (this._signatures?.length > 0) {
74+
this.transaction.constructSignedPayload(this._signatures[0].signature);
75+
}
76+
this.transaction.loadInputsAndOutputs();
77+
return this.transaction;
78+
}
79+
80+
/** @inheritdoc */
81+
protected signImplementation(key: BaseKey): DelegateTransaction {
82+
this._signer = new KeyPair({ prv: key.key });
83+
return this._delegateTransaction;
84+
}
85+
86+
// region Getters and Setters
87+
/** @inheritdoc */
88+
protected get transaction(): DelegateTransaction {
89+
return this._delegateTransaction;
90+
}
91+
92+
/** @inheritdoc */
93+
protected set transaction(transaction: DelegateTransaction) {
94+
this._delegateTransaction = transaction;
95+
}
96+
97+
// endregion
98+
99+
// region Validators
100+
/** @inheritdoc */
101+
validateAddress(address: BaseAddress, addressFormat?: string): void {
102+
if (!utils.isValidAddress(address.address)) {
103+
throw new AddressValidationError(address.address);
104+
}
105+
}
106+
107+
/** @inheritdoc */
108+
validateKey(key: BaseKey): void {
109+
try {
110+
new KeyPair({ prv: key.key });
111+
} catch {
112+
throw new BuildTransactionError(`Key validation failed`);
113+
}
114+
}
115+
116+
/** @inheritdoc */
117+
validateRawTransaction(rawTransaction: any): void {
118+
try {
119+
nearAPI.utils.serialize.deserialize(nearAPI.transactions.SCHEMA.SignedDelegate, rawTransaction);
120+
} catch {
121+
try {
122+
nearAPI.utils.serialize.deserialize(nearAPI.transactions.SCHEMA.DelegateAction, rawTransaction);
123+
} catch {
124+
throw new BuildTransactionError('invalid raw transaction');
125+
}
126+
}
127+
}
128+
129+
/** @inheritdoc */
130+
validateTransaction(transaction: DelegateTransaction): void {
131+
if (!transaction.nearTransaction) {
132+
return;
133+
}
134+
this.validateAddress({ address: transaction.nearTransaction.senderId });
135+
this.validateAddress({ address: transaction.nearTransaction.receiverId });
136+
}
137+
138+
/** @inheritdoc */
139+
validateValue(value: BigNumber): void {
140+
if (value.isLessThan(0)) {
141+
throw new BuildTransactionError('Value cannot be less than zero');
142+
}
143+
}
144+
145+
// endregion
146+
147+
/**
148+
* Sets the public key and the address of the sender of this transaction.
149+
*
150+
* @param {string} address the account that is sending this transaction
151+
* @param {string} pubKey the public key that is sending this transaction
152+
* @returns {TransactionBuilder} This transaction builder
153+
*/
154+
public sender(address: string, pubKey: string): this {
155+
if (!address || !utils.isValidAddress(address.toString())) {
156+
throw new BuildTransactionError('Invalid or missing address, got: ' + address);
157+
}
158+
if (!pubKey || !utils.isValidPublicKey(pubKey)) {
159+
throw new BuildTransactionError('Invalid or missing pubKey, got: ' + pubKey);
160+
}
161+
this._sender = address;
162+
this._publicKey = pubKey;
163+
return this;
164+
}
165+
166+
/**
167+
* Sets the account Id of the receiver of this transaction.
168+
*
169+
* @param {string} accountId the account id of the account that is receiving this transaction
170+
* @returns {TransactionBuilder} This transaction builder
171+
*/
172+
public receiverId(accountId: string): this {
173+
utils.isValidAddress(accountId);
174+
this._receiverId = accountId;
175+
return this;
176+
}
177+
178+
/**
179+
* Set the nonce
180+
*
181+
* @param {bigint} nonce - number that can be only used once
182+
* @returns {TransactionBuilder} This transaction builder
183+
*/
184+
public nonce(nonce: bigint): this {
185+
if (nonce < 0) {
186+
throw new BuildTransactionError(`Invalid nonce: ${nonce}`);
187+
}
188+
this._nonce = nonce;
189+
return this;
190+
}
191+
192+
/**
193+
* Sets the blockHash of this transaction.
194+
*
195+
* @param {string} blockHeight the blockHeight of this transaction
196+
* @returns {TransactionBuilder} This transaction builder
197+
*/
198+
public recentBlockHeight(blockHeight: bigint): this {
199+
this._recentBlockHeight = blockHeight;
200+
return this;
201+
}
202+
203+
/**
204+
* Sets the list of actions of this transaction.
205+
*
206+
* @param {nearAPI.transactions.Action[]} value the the list of actions
207+
* @returns {TransactionBuilder} This transaction builder
208+
*/
209+
protected actions(value: nearAPI.transactions.Action[]): this {
210+
this._actions = value;
211+
return this;
212+
}
213+
/**
214+
* Builds the NEAR transaction.
215+
*
216+
* @return {Transaction} near sdk transaction
217+
*/
218+
protected buildNearTransaction(): DelegateAction {
219+
assert(this._sender, new BuildTransactionError('sender is required before building'));
220+
assert(this._recentBlockHeight, new BuildTransactionError('recent block height is required before building'));
221+
222+
const tx = new DelegateAction({
223+
senderId: this._sender,
224+
receiverId: this._receiverId,
225+
actions: this._actions,
226+
nonce: this._nonce,
227+
maxBlockHeight: BigInt(BLOCK_HEIGHT_TTL + this._recentBlockHeight),
228+
publicKey: nearAPI.utils.PublicKey.fromString(nearAPI.utils.serialize.base_encode(hex.decode(this._publicKey))),
229+
});
230+
231+
return tx;
232+
}
233+
234+
/** @inheritDoc */
235+
addSignature(publicKey: BasePublicKey, signature: Buffer): void {
236+
this._signatures.push({ publicKey, signature });
237+
}
238+
}

modules/sdk-coin-near/src/lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ export const AdditionalAllowedMethods = ['ft_transfer', 'storage_deposit'];
1414
export const FT_TRANSFER = 'ft_transfer';
1515

1616
export const HEX_REGEX = /^[0-9a-fA-F]+$/;
17+
18+
export const BLOCK_HEIGHT_TTL = 120n;

0 commit comments

Comments
 (0)