Skip to content

Commit 0691236

Browse files
committed
feat: add stakingBuilder (bond and bondExtra) for polyx
TICKET: SC-1826
1 parent 9bf6627 commit 0691236

File tree

8 files changed

+576
-0
lines changed

8 files changed

+576
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,13 @@ export interface RegisterDidWithCDDArgs extends Args {
55
secondaryKeys: [];
66
expiry: null;
77
}
8+
9+
export interface BondArgs extends Args {
10+
controller: string;
11+
value: string;
12+
payee: string;
13+
}
14+
15+
export interface BondExtraArgs extends Args {
16+
maxAdditional: string;
17+
}

modules/sdk-coin-polyx/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export {
1111
export { TransactionBuilderFactory } from './transactionBuilderFactory';
1212
export { TransferBuilder } from './transferBuilder';
1313
export { RegisterDidWithCDDBuilder } from './registerDidWithCDDBuilder';
14+
export { StakingBuilder } from './stakingBuilder';
1415
export { Utils, default as utils } from './utils';
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import BigNumber from 'bignumber.js';
2+
import { TransactionBuilder, Transaction } from '@bitgo/abstract-substrate';
3+
import { DecodedSignedTx, DecodedSigningPayload, UnsignedTransaction } from '@substrate/txwrapper-core';
4+
import { methods } from '@substrate/txwrapper-polkadot';
5+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
6+
import { BaseAddress, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
7+
import { StakingTransactionSchema } from './txnSchema';
8+
import utils from './utils';
9+
import { BondArgs, BondExtraArgs } from './iface';
10+
11+
export interface StakingParams {
12+
amount: string;
13+
controller?: string;
14+
payee?: string;
15+
addToStake?: boolean;
16+
}
17+
18+
export class StakingBuilder extends TransactionBuilder {
19+
protected _amount: string;
20+
protected _controller: string;
21+
protected _payee: string;
22+
protected _addToStake: boolean;
23+
24+
constructor(_coinConfig: Readonly<CoinConfig>) {
25+
super(_coinConfig);
26+
this.material(utils.getMaterial(_coinConfig.network.type));
27+
}
28+
29+
protected get transactionType(): TransactionType {
30+
return TransactionType.StakingActivate;
31+
}
32+
33+
/**
34+
* Build the appropriate staking transaction based on whether we're doing
35+
* initial bonding or adding to an existing stake.
36+
*
37+
* @returns {UnsignedTransaction} The constructed unsigned staking transaction
38+
*/
39+
protected buildTransaction(): UnsignedTransaction {
40+
const baseTxInfo = this.createBaseTxInfo();
41+
42+
if (this._addToStake) {
43+
return methods.staking.bondExtra(
44+
{
45+
maxAdditional: this._amount,
46+
},
47+
baseTxInfo.baseTxInfo,
48+
baseTxInfo.options
49+
);
50+
} else {
51+
return methods.staking.bond(
52+
{
53+
controller: this._controller || this._sender,
54+
value: this._amount,
55+
payee: this._payee || 'Staked',
56+
},
57+
baseTxInfo.baseTxInfo,
58+
baseTxInfo.options
59+
);
60+
}
61+
}
62+
63+
/**
64+
* Set the staking amount.
65+
*
66+
* @param {string} amount - Amount to stake in smallest unit
67+
* @returns {StakingBuilder} This staking builder
68+
*/
69+
amount(amount: string): this {
70+
this.validateValue(new BigNumber(amount));
71+
this._amount = amount;
72+
return this;
73+
}
74+
75+
/**
76+
* Set whether this is adding to an existing stake.
77+
*
78+
* @param {boolean} addToStake - true for bondExtra, false for initial bond
79+
* @returns {StakingBuilder} This staking builder
80+
*/
81+
addToStake(addToStake: boolean): this {
82+
this._addToStake = addToStake;
83+
return this;
84+
}
85+
86+
/**
87+
* The controller of the staked amount.
88+
* Only applies to initial bonding.
89+
*
90+
* @param {BaseAddress} controller - Controller account address
91+
* @returns {StakingBuilder} This staking builder
92+
*/
93+
controller(controller: BaseAddress): this {
94+
this.validateAddress(controller);
95+
this._controller = controller.address;
96+
return this;
97+
}
98+
99+
/**
100+
* The rewards destination of the staked amount.
101+
* Only applies to initial bonding.
102+
*
103+
* @param {string} payee - Where rewards should be directed (e.g. 'Staked')
104+
* @returns {StakingBuilder} This staking builder
105+
*/
106+
payee(payee: string): this {
107+
this._payee = payee;
108+
return this;
109+
}
110+
111+
/** @inheritdoc */
112+
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx): void {
113+
const methodName = decodedTxn.method?.name as string;
114+
115+
if (methodName === 'staking.bond') {
116+
const txMethod = decodedTxn.method.args as unknown as BondArgs;
117+
const value = txMethod.value;
118+
const controller = txMethod.controller;
119+
const payee = txMethod.payee;
120+
121+
const validationResult = StakingTransactionSchema.validate({
122+
value,
123+
controller,
124+
payee,
125+
});
126+
127+
if (validationResult.error) {
128+
throw new InvalidTransactionError(`Invalid transaction: ${validationResult.error.message}`);
129+
}
130+
} else if (methodName === 'staking.bondExtra') {
131+
const txMethod = decodedTxn.method.args as unknown as BondExtraArgs;
132+
const value = txMethod.maxAdditional;
133+
134+
const validationResult = StakingTransactionSchema.validate({
135+
value,
136+
addToStake: true,
137+
});
138+
139+
if (validationResult.error) {
140+
throw new InvalidTransactionError(`Invalid transaction: ${validationResult.error.message}`);
141+
}
142+
} else {
143+
throw new InvalidTransactionError(`Invalid transaction type: ${methodName}`);
144+
}
145+
}
146+
147+
/** @inheritdoc */
148+
protected fromImplementation(rawTransaction: string): Transaction {
149+
const tx = super.fromImplementation(rawTransaction);
150+
151+
const methodName = this._method?.name as string;
152+
153+
if (methodName === 'staking.bond' && this._method) {
154+
const txMethod = this._method.args as unknown as BondArgs;
155+
this.amount(txMethod.value);
156+
this.controller({ address: txMethod.controller });
157+
this.payee(txMethod.payee);
158+
} else if (methodName === 'staking.bondExtra' && this._method) {
159+
const txMethod = this._method.args as unknown as BondExtraArgs;
160+
this.amount(txMethod.maxAdditional);
161+
this.addToStake(true);
162+
} else {
163+
throw new InvalidTransactionError(
164+
`Invalid Transaction Type: ${methodName}. Expected staking.bond or staking.bondExtra`
165+
);
166+
}
167+
168+
return tx;
169+
}
170+
171+
/** @inheritdoc */
172+
validateTransaction(tx: Transaction): void {
173+
super.validateTransaction(tx);
174+
this.validateFields();
175+
}
176+
177+
/**
178+
* Validate the builder fields.
179+
*/
180+
private validateFields(): void {
181+
const validationResult = StakingTransactionSchema.validate({
182+
value: this._amount,
183+
controller: this._controller,
184+
payee: this._payee,
185+
addToStake: this._addToStake,
186+
});
187+
188+
if (validationResult.error) {
189+
throw new InvalidTransactionError(`Invalid transaction: ${validationResult.error.message}`);
190+
}
191+
}
192+
}

modules/sdk-coin-polyx/src/lib/transactionBuilderFactory.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { decode } from '@substrate/txwrapper-polkadot';
44
import { TransferBuilder } from './transferBuilder';
55
import { RegisterDidWithCDDBuilder } from './registerDidWithCDDBuilder';
6+
import { StakingBuilder } from './stakingBuilder';
67
import utils from './utils';
78
import { Interface, SingletonRegistry, TransactionBuilder } from './';
89

@@ -22,6 +23,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
2223
return new RegisterDidWithCDDBuilder(this._coinConfig).material(this._material);
2324
}
2425

26+
getStakingBuilder(): StakingBuilder {
27+
return new StakingBuilder(this._coinConfig).material(this._material);
28+
}
29+
2530
getWalletInitializationBuilder(): void {
2631
throw new NotImplementedError(`walletInitialization for ${this._coinConfig.name} not implemented`);
2732
}
@@ -49,6 +54,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
4954
return this.getTransferBuilder();
5055
} else if (methodName === Interface.MethodNames.RegisterDidWithCDD) {
5156
return this.getRegisterDidWithCDDBuilder();
57+
} else if (methodName === 'staking.bond' || methodName === 'staking.bondExtra') {
58+
return this.getStakingBuilder();
5259
} else {
5360
throw new Error('Transaction cannot be parsed or has an unsupported transaction type');
5461
}

modules/sdk-coin-polyx/src/lib/txnSchema.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,18 @@ export const RegisterDidWithCDDTransactionSchema = joi.object({
88
secondaryKeys: joi.array().length(0).required(),
99
expiry: joi.valid(null).required(),
1010
});
11+
12+
export const StakingTransactionSchema = joi.object({
13+
value: joi.string().required(),
14+
controller: joi.alternatives().conditional('addToStake', {
15+
is: true,
16+
then: joi.optional(),
17+
otherwise: addressSchema.required(),
18+
}),
19+
payee: joi.alternatives().conditional('addToStake', {
20+
is: true,
21+
then: joi.optional(),
22+
otherwise: joi.string().required(),
23+
}),
24+
addToStake: joi.boolean().optional(),
25+
});

modules/sdk-coin-polyx/src/polyx.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '@bitgo/sdk-core';
1212
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
1313
import { SubstrateCoin } from '@bitgo/abstract-substrate';
14+
import { StakingBuilder } from './lib/stakingBuilder';
1415

1516
export class Polyx extends SubstrateCoin {
1617
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -70,4 +71,8 @@ export class Polyx extends SubstrateCoin {
7071
signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
7172
throw new Error('Method not implemented.');
7273
}
74+
75+
stake(): StakingBuilder {
76+
return this.getBuilder().getStakingBuilder();
77+
}
7378
}

modules/sdk-coin-polyx/test/resources/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,18 @@ export const rawTx = {
4040
},
4141
};
4242

43+
export const stakingTx = {
44+
bond: {
45+
unsigned: '0x9c04110000bec110eab4d327d3b2b6bb68e888654a474694d3935ce35bd3926e4bc7ebd538419c00',
46+
signed:
47+
'0x35028400bec110eab4d327d3b2b6bb68e888654a474694d3935ce35bd3926e4bc7ebd5380100066429bde7f75f50a792dba137a524611debd57018c908f8daa6f94a6e246ac60d839b69f0f7df6f916913d9dccb709ad56857f866c3775d7ffcb5d764f38fe5020c00110000bec110eab4d327d3b2b6bb68e888654a474694d3935ce35bd3926e4bc7ebd538419c00',
48+
},
49+
bondExtra: {
50+
unsigned:
51+
'0xad018400bec110eab4d327d3b2b6bb68e888654a474694d3935ce35bd3926e4bc7ebd53801201199a43e8ee9fec776ac0045120e54b61edd4da8b949993772c7d0184e682a5fd3e2f64e0164a10b9ff837928c8b658398b292012bab5b7a377e654a302b81750108001101a10f',
52+
signed:
53+
'0xad018400bec110eab4d327d3b2b6bb68e888654a474694d3935ce35bd3926e4bc7ebd53801201199a43e8ee9fec776ac0045120e54b61edd4da8b949993772c7d0184e682a5fd3e2f64e0164a10b9ff837928c8b658398b292012bab5b7a377e654a302b81750108001101a10f',
54+
},
55+
};
56+
4357
export const { txVersion, specVersion, genesisHash, chainName, specName } = Networks.test.polyx;

0 commit comments

Comments
 (0)