Skip to content

Commit ea83103

Browse files
committed
feat(sdk-coin-polyx): add rejectInstruction transaction
TICKET: WIN-7618
1 parent e75126e commit ea83103

File tree

9 files changed

+312
-3
lines changed

9 files changed

+312
-3
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface TxData extends Interface.TxData {
1212
assetId?: string;
1313
fromDID?: string;
1414
toDID?: string;
15+
instructionId?: string;
16+
portfolioDID?: string;
1517
}
1618

1719
/**
@@ -49,6 +51,8 @@ export const MethodNames = {
4951
PreApproveAsset: 'preApproveAsset' as const,
5052

5153
AddAndAffirmWithMediators: 'addAndAffirmWithMediators' as const,
54+
55+
RejectInstruction: 'rejectInstruction' as const,
5256
} as const;
5357

5458
// Create a type that represents the keys of this object
@@ -85,6 +89,12 @@ export interface AddAndAffirmWithMediatorsArgs extends Args {
8589
mediators: [];
8690
}
8791

92+
export interface RejectInstructionBuilderArgs extends Args {
93+
id: string;
94+
portfolio: { did: string; kind: PortfolioKind.Default };
95+
numberOfAssets: { fungible: number; nonFungible: number; offChain: number };
96+
}
97+
8898
export interface TxMethod extends Omit<Interface.TxMethod, 'args' | 'name'> {
8999
args:
90100
| Interface.TransferArgs
@@ -100,7 +110,8 @@ export interface TxMethod extends Omit<Interface.TxMethod, 'args' | 'name'> {
100110
| Interface.BatchArgs
101111
| RegisterDidWithCDDArgs
102112
| PreApproveAssetArgs
103-
| AddAndAffirmWithMediatorsArgs;
113+
| AddAndAffirmWithMediatorsArgs
114+
| RejectInstructionBuilderArgs;
104115
name: MethodNamesValues;
105116
}
106117

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { TransferBuilder } from './transferBuilder';
1414
export { RegisterDidWithCDDBuilder } from './registerDidWithCDDBuilder';
1515
export { PreApproveAssetBuilder } from './preApproveAssetBuilder';
1616
export { TokenTransferBuilder } from './tokenTransferBuilder';
17+
export { RejectInstructionBuilder } from './rejectInstructionBuilder';
1718
export { Transaction as PolyxTransaction } from './transaction';
1819
export { BondExtraBuilder } from './bondExtraBuilder';
1920
export { BatchStakingBuilder as BatchBuilder } from './batchStakingBuilder';
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { TransactionType } from '@bitgo/sdk-core';
2+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3+
import { PolyxBaseBuilder } from './baseBuilder';
4+
import { TxMethod, MethodNames, RejectInstructionBuilderArgs, PortfolioKind } from './iface';
5+
import { Transaction } from './transaction';
6+
import { Interface } from '@bitgo/abstract-substrate';
7+
import { RejectInstructionTransactionSchema } from './txnSchema';
8+
import { DecodedSignedTx, DecodedSigningPayload, defineMethod, UnsignedTransaction } from '@substrate/txwrapper-core';
9+
10+
export class RejectInstructionBuilder extends PolyxBaseBuilder<TxMethod, Transaction> {
11+
protected _instructionId: string;
12+
protected _portfolioDID: string;
13+
14+
constructor(_coinConfig: Readonly<CoinConfig>) {
15+
super(_coinConfig);
16+
this._transaction = new Transaction(_coinConfig);
17+
}
18+
19+
protected get transactionType(): TransactionType {
20+
return TransactionType.RejectInstruction;
21+
}
22+
23+
protected buildTransaction(): UnsignedTransaction {
24+
const baseTxInfo = this.createBaseTxInfo();
25+
return this.rejectInstruction(
26+
{
27+
id: this._instructionId,
28+
portfolio: {
29+
did: this._portfolioDID,
30+
kind: PortfolioKind.Default,
31+
},
32+
numberOfAssets: {
33+
fungible: 1,
34+
nonFungible: 0,
35+
offChain: 0,
36+
},
37+
},
38+
baseTxInfo
39+
);
40+
}
41+
42+
/**
43+
* @param instructionId - The ID of the instruction to be rejected
44+
* @returns {this}
45+
*/
46+
instructionId(instructionId: string): this {
47+
this._instructionId = instructionId;
48+
return this;
49+
}
50+
51+
/**
52+
* @param portfolioDID - The DID of the portfolio associated with the instruction
53+
* @returns {this}
54+
*/
55+
portfolioDID(portfolioDID: string): this {
56+
this._portfolioDID = portfolioDID;
57+
return this;
58+
}
59+
60+
/** @inheritdoc */
61+
protected fromImplementation(rawTransaction: string): Transaction {
62+
const tx = super.fromImplementation(rawTransaction);
63+
if (this._method?.name === MethodNames.RejectInstruction) {
64+
const txMethod = this._method.args as RejectInstructionBuilderArgs;
65+
this._instructionId = txMethod.id as string;
66+
this._portfolioDID = txMethod.portfolio.did as string;
67+
} else {
68+
throw new Error(`Cannot build from transaction with method ${this._method?.name} for RejectInstructionBuilder`);
69+
}
70+
return tx;
71+
}
72+
73+
/** @inheritdoc */
74+
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx, rawTransaction?: string): void {
75+
if (decodedTxn.method?.name === MethodNames.RejectInstruction) {
76+
const txMethod = decodedTxn.method.args as RejectInstructionBuilderArgs;
77+
const id = txMethod.id;
78+
const portfolio = txMethod.portfolio;
79+
80+
const validationResult = RejectInstructionTransactionSchema.validate({
81+
id,
82+
portfolio,
83+
});
84+
if (validationResult.error) {
85+
throw new Error(`Invalid transaction: ${validationResult.error.message}`);
86+
}
87+
}
88+
}
89+
90+
private rejectInstruction(args: RejectInstructionBuilderArgs, info: Interface.CreateBaseTxInfo): UnsignedTransaction {
91+
return defineMethod(
92+
{
93+
method: {
94+
args,
95+
name: 'rejectInstruction',
96+
pallet: 'settlement',
97+
},
98+
...info.baseTxInfo,
99+
},
100+
info.options
101+
);
102+
}
103+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ export class TokenTransferBuilder extends PolyxBaseBuilder<TxMethod, Transaction
137137
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx, rawTransaction?: string): void {
138138
if (decodedTxn.method?.name === MethodNames.AddAndAffirmWithMediators) {
139139
const txMethod = decodedTxn.method.args as AddAndAffirmWithMediatorsArgs;
140-
console.log(`Validating transaction: ${JSON.stringify(txMethod)}`);
141140
const venueId = txMethod.venueId;
142141
const settlementType = txMethod.settlementType;
143142
const tradeDate = txMethod.tradeDate;

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { Transaction as SubstrateTransaction, utils, KeyPair } from '@bitgo/abst
22
import { InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
33
import { construct, decode } from '@substrate/txwrapper-polkadot';
44
import { decodeAddress } from '@polkadot/keyring';
5-
import { DecodedTx, RegisterDidWithCDDArgs, PreApproveAssetArgs, TxData, AddAndAffirmWithMediatorsArgs } from './iface';
5+
import {
6+
DecodedTx,
7+
RegisterDidWithCDDArgs,
8+
PreApproveAssetArgs,
9+
TxData,
10+
AddAndAffirmWithMediatorsArgs,
11+
RejectInstructionBuilderArgs,
12+
} from './iface';
613
import polyxUtils from './utils';
714

815
export class Transaction extends SubstrateTransaction {
@@ -63,6 +70,11 @@ export class Transaction extends SubstrateTransaction {
6370
result.amount = sendTokenArgs.legs[0].fungible.amount.toString();
6471
result.assetId = sendTokenArgs.legs[0].fungible.assetId;
6572
result.memo = sendTokenArgs.instructionMemo;
73+
} else if (this.type === TransactionType.RejectInstruction) {
74+
const rejectInstructionArgs = txMethod as RejectInstructionBuilderArgs;
75+
result.instructionId = rejectInstructionArgs.id as string;
76+
result.portfolioDID = rejectInstructionArgs.portfolio.did as string;
77+
result.amount = '0'; // Reject instruction does not transfer any value
6678
} else {
6779
return super.toJson() as TxData;
6880
}
@@ -88,6 +100,8 @@ export class Transaction extends SubstrateTransaction {
88100
this.decodeInputsAndOutputsForPreApproveAsset(decodedTx);
89101
} else if (this.type === TransactionType.SendToken) {
90102
this.decodeInputsAndOutputsForSendToken(decodedTx);
103+
} else if (this.type === TransactionType.RejectInstruction) {
104+
this.decodeInputsAndOutputsForRejectInstruction(decodedTx);
91105
}
92106
}
93107

@@ -148,4 +162,22 @@ export class Transaction extends SubstrateTransaction {
148162
coin: this._coinConfig.name,
149163
});
150164
}
165+
166+
private decodeInputsAndOutputsForRejectInstruction(decodedTx: DecodedTx) {
167+
const txMethod = decodedTx.method.args as RejectInstructionBuilderArgs;
168+
const portfolioDID = txMethod.portfolio.did;
169+
const value = '0'; // Reject instruction does not transfer any value
170+
171+
this._inputs.push({
172+
address: portfolioDID,
173+
value,
174+
coin: this._coinConfig.name,
175+
});
176+
177+
this._outputs.push({
178+
address: portfolioDID,
179+
value,
180+
coin: this._coinConfig.name,
181+
});
182+
}
151183
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ export const AddAndAffirmWithMediatorsTransactionSchema = joi.object({
9393
mediators: joi.array().length(0).required(),
9494
});
9595

96+
export const RejectInstructionTransactionSchema = joi.object({
97+
id: joi.string().required(),
98+
portfolio: joi
99+
.object({
100+
did: addressSchema.required(),
101+
kind: joi
102+
.object({
103+
default: joi.valid(null),
104+
})
105+
.required(),
106+
})
107+
.required(),
108+
numberOfAssets: joi
109+
.object({
110+
fungible: joi.number().required(),
111+
nonFungible: joi.number().required(),
112+
offChain: joi.number().required(),
113+
})
114+
.optional(),
115+
});
116+
96117
// For standalone bondExtra transactions
97118
export const BondExtraTransactionSchema = joi.object({
98119
value: joi.string().required(),

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ export const rawTx = {
9191
unsigned:
9292
'0xb90225140000000004001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db83634200bc6f7ec808f361c1353ab9dc88c3cc54b98d9eb60fed9c063e67a40925b8ef6100780602887b358cf48989d0d9aa6c8d2840420f00000000000000000000000000041208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db836342000130000000000000000000000000000000000000000000000000000000000000000055000c007bdb6a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d639fe96f0dab5a96e118de830dc1f5d0105adeae3f3208ce95e8e03494456e191',
9393
},
94+
rejectInstruction: {
95+
signed:
96+
'0x49028400e8164bbe81964be28292d96f62c2ef6117d911638f38ae1b1bbe69df0b6df127004a08d9ccbb4b157c3df4b3e7a8bc602e6f5fab075a4bf36b29963a9a8926d16b280c2e514e534d42bcf3173372c619449c595d6183a9fbe5f9a8efe9c5b6100965034800250d14370000000000001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db83634200',
97+
unsigned:
98+
'0xac250d14370000000000001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db8363420025014c007bdb6a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d6ffecb4672251137ef38c9aca7c031e578ae13c376c1699fc9cc094c29e16c7df',
99+
},
94100
};
95101

96102
export const stakingTx = {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import should from 'should';
2+
import { RejectInstructionBuilder } from '../../../src/lib';
3+
import { utils } from '../../../src';
4+
5+
import { accounts, rawTx, chainName, genesisHash, mockTssSignature } from '../../resources';
6+
import { buildTestConfig } from './base';
7+
import { testnetMaterial } from '../../../src/resources';
8+
9+
describe('Polyx Reject Instruction Builder - Testnet', () => {
10+
let builder: RejectInstructionBuilder;
11+
12+
const sender = accounts.account1;
13+
const instructionId = '14100';
14+
const portfolioDID = '0x1208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db836342';
15+
16+
beforeEach(() => {
17+
const config = buildTestConfig();
18+
builder = new RejectInstructionBuilder(config).material(utils.getMaterial(config.network.type));
19+
});
20+
21+
describe('build rejectInstruction transaction', () => {
22+
it('should build a rejectInstruction transaction', async () => {
23+
builder
24+
.instructionId(instructionId)
25+
.portfolioDID(portfolioDID)
26+
.sender({ address: sender.address })
27+
.validity({ firstValid: 3933, maxDuration: 64 })
28+
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
29+
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
30+
.fee({ amount: 0, type: 'tip' });
31+
builder.addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex'));
32+
const tx = await builder.build();
33+
const txJson = tx.toJson();
34+
should.deepEqual(txJson.instructionId, instructionId);
35+
should.deepEqual(txJson.portfolioDID, portfolioDID);
36+
should.deepEqual(txJson.sender, sender.address);
37+
should.deepEqual(txJson.blockNumber, 3933);
38+
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
39+
should.deepEqual(txJson.genesisHash, genesisHash);
40+
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
41+
should.deepEqual(txJson.nonce, 200);
42+
should.deepEqual(txJson.tip, 0);
43+
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
44+
should.deepEqual(txJson.chainName, testnetMaterial.chainName);
45+
should.deepEqual(txJson.eraPeriod, 64);
46+
});
47+
48+
it('should build an unsigned rejectInstruction transaction', async () => {
49+
builder
50+
.instructionId(instructionId)
51+
.portfolioDID(portfolioDID)
52+
.sender({ address: sender.address })
53+
.validity({ firstValid: 3933, maxDuration: 64 })
54+
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
55+
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
56+
.fee({ amount: 0, type: 'tip' });
57+
const tx = await builder.build();
58+
const txJson = tx.toJson();
59+
should.deepEqual(txJson.instructionId, instructionId);
60+
should.deepEqual(txJson.portfolioDID, portfolioDID);
61+
should.deepEqual(txJson.sender, sender.address);
62+
should.deepEqual(txJson.blockNumber, 3933);
63+
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
64+
should.deepEqual(txJson.genesisHash, genesisHash);
65+
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
66+
should.deepEqual(txJson.nonce, 200);
67+
should.deepEqual(txJson.tip, 0);
68+
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
69+
should.deepEqual(txJson.chainName, chainName);
70+
should.deepEqual(txJson.eraPeriod, 64);
71+
});
72+
73+
it('should build from raw signed tx', async () => {
74+
if (rawTx.rejectInstruction?.signed) {
75+
builder.from(rawTx.rejectInstruction.signed);
76+
builder
77+
.validity({ firstValid: 3933, maxDuration: 64 })
78+
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
79+
const tx = await builder.build();
80+
const txJson = tx.toJson();
81+
should.exist(txJson.instructionId);
82+
should.deepEqual(txJson.instructionId, instructionId);
83+
should.exist(txJson.portfolioDID);
84+
should.deepEqual(txJson.portfolioDID, portfolioDID);
85+
should.deepEqual(txJson.blockNumber, 3933);
86+
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
87+
should.deepEqual(txJson.genesisHash, genesisHash);
88+
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
89+
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
90+
should.deepEqual(txJson.chainName, chainName);
91+
should.deepEqual(txJson.eraPeriod, 64);
92+
}
93+
});
94+
95+
it('should build from raw unsigned tx', async () => {
96+
if (rawTx.rejectInstruction?.unsigned) {
97+
builder.from(rawTx.rejectInstruction.unsigned);
98+
builder
99+
.validity({ firstValid: 3933, maxDuration: 64 })
100+
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
101+
.sender({ address: sender.address })
102+
.addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex'));
103+
104+
const tx = await builder.build();
105+
const txJson = tx.toJson();
106+
should.exist(txJson.instructionId);
107+
should.deepEqual(txJson.instructionId, instructionId);
108+
should.exist(txJson.portfolioDID);
109+
should.deepEqual(txJson.portfolioDID, portfolioDID);
110+
should.deepEqual(txJson.sender, sender.address);
111+
should.deepEqual(txJson.blockNumber, 3933);
112+
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
113+
should.deepEqual(txJson.genesisHash, genesisHash);
114+
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
115+
should.deepEqual(txJson.eraPeriod, 64);
116+
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
117+
should.deepEqual(txJson.chainName, chainName);
118+
}
119+
});
120+
121+
it('should validate instruction ID is set', async () => {
122+
builder
123+
.portfolioDID(portfolioDID)
124+
.sender({ address: sender.address })
125+
.validity({ firstValid: 3933, maxDuration: 64 })
126+
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
127+
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
128+
.fee({ amount: 0, type: 'tip' });
129+
130+
await builder.build().should.be.rejected();
131+
});
132+
});
133+
});

0 commit comments

Comments
 (0)