Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion modules/sdk-coin-polyx/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface TxData extends Interface.TxData {
assetId?: string;
fromDID?: string;
toDID?: string;
instructionId?: string;
portfolioDID?: string;
}

/**
Expand Down Expand Up @@ -49,6 +51,8 @@ export const MethodNames = {
PreApproveAsset: 'preApproveAsset' as const,

AddAndAffirmWithMediators: 'addAndAffirmWithMediators' as const,

RejectInstruction: 'rejectInstruction' as const,
} as const;

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

export interface RejectInstructionBuilderArgs extends Args {
id: string;
portfolio: { did: string; kind: PortfolioKind.Default };
numberOfAssets: { fungible: number; nonFungible: number; offChain: number };
}

export interface TxMethod extends Omit<Interface.TxMethod, 'args' | 'name'> {
args:
| Interface.TransferArgs
Expand All @@ -100,7 +110,8 @@ export interface TxMethod extends Omit<Interface.TxMethod, 'args' | 'name'> {
| Interface.BatchArgs
| RegisterDidWithCDDArgs
| PreApproveAssetArgs
| AddAndAffirmWithMediatorsArgs;
| AddAndAffirmWithMediatorsArgs
| RejectInstructionBuilderArgs;
name: MethodNamesValues;
}

Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-polyx/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { TransferBuilder } from './transferBuilder';
export { RegisterDidWithCDDBuilder } from './registerDidWithCDDBuilder';
export { PreApproveAssetBuilder } from './preApproveAssetBuilder';
export { TokenTransferBuilder } from './tokenTransferBuilder';
export { RejectInstructionBuilder } from './rejectInstructionBuilder';
export { Transaction as PolyxTransaction } from './transaction';
export { BondExtraBuilder } from './bondExtraBuilder';
export { BatchStakingBuilder as BatchBuilder } from './batchStakingBuilder';
Expand Down
103 changes: 103 additions & 0 deletions modules/sdk-coin-polyx/src/lib/rejectInstructionBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { PolyxBaseBuilder } from './baseBuilder';
import { TxMethod, MethodNames, RejectInstructionBuilderArgs, PortfolioKind } from './iface';
import { Transaction } from './transaction';
import { Interface } from '@bitgo/abstract-substrate';
import { RejectInstructionTransactionSchema } from './txnSchema';
import { DecodedSignedTx, DecodedSigningPayload, defineMethod, UnsignedTransaction } from '@substrate/txwrapper-core';

export class RejectInstructionBuilder extends PolyxBaseBuilder<TxMethod, Transaction> {
protected _instructionId: string;
protected _portfolioDID: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._transaction = new Transaction(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.RejectInstruction;
}

protected buildTransaction(): UnsignedTransaction {
const baseTxInfo = this.createBaseTxInfo();
return this.rejectInstruction(
{
id: this._instructionId,
portfolio: {
did: this._portfolioDID,
kind: PortfolioKind.Default,
},
numberOfAssets: {
fungible: 1,
nonFungible: 0,
offChain: 0,
},
},
baseTxInfo
);
}

/**
* @param instructionId - The ID of the instruction to be rejected
* @returns {this}
*/
instructionId(instructionId: string): this {
this._instructionId = instructionId;
return this;
}

/**
* @param portfolioDID - The DID of the portfolio associated with the instruction
* @returns {this}
*/
portfolioDID(portfolioDID: string): this {
this._portfolioDID = portfolioDID;
return this;
}

/** @inheritdoc */
protected fromImplementation(rawTransaction: string): Transaction {
const tx = super.fromImplementation(rawTransaction);
if (this._method?.name === MethodNames.RejectInstruction) {
const txMethod = this._method.args as RejectInstructionBuilderArgs;
this._instructionId = txMethod.id as string;
this._portfolioDID = txMethod.portfolio.did as string;
} else {
throw new Error(`Cannot build from transaction with method ${this._method?.name} for RejectInstructionBuilder`);
}
return tx;
}

/** @inheritdoc */
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx, rawTransaction?: string): void {
if (decodedTxn.method?.name === MethodNames.RejectInstruction) {
const txMethod = decodedTxn.method.args as RejectInstructionBuilderArgs;
const id = txMethod.id;
const portfolio = txMethod.portfolio;

const validationResult = RejectInstructionTransactionSchema.validate({
id,
portfolio,
});
if (validationResult.error) {
throw new Error(`Invalid transaction: ${validationResult.error.message}`);
}
}
}

private rejectInstruction(args: RejectInstructionBuilderArgs, info: Interface.CreateBaseTxInfo): UnsignedTransaction {
return defineMethod(
{
method: {
args,
name: 'rejectInstruction',
pallet: 'settlement',
},
...info.baseTxInfo,
},
info.options
);
}
}
1 change: 0 additions & 1 deletion modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ export class TokenTransferBuilder extends PolyxBaseBuilder<TxMethod, Transaction
validateDecodedTransaction(decodedTxn: DecodedSigningPayload | DecodedSignedTx, rawTransaction?: string): void {
if (decodedTxn.method?.name === MethodNames.AddAndAffirmWithMediators) {
const txMethod = decodedTxn.method.args as AddAndAffirmWithMediatorsArgs;
console.log(`Validating transaction: ${JSON.stringify(txMethod)}`);
const venueId = txMethod.venueId;
const settlementType = txMethod.settlementType;
const tradeDate = txMethod.tradeDate;
Expand Down
34 changes: 33 additions & 1 deletion modules/sdk-coin-polyx/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { Transaction as SubstrateTransaction, utils, KeyPair } from '@bitgo/abst
import { InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
import { construct, decode } from '@substrate/txwrapper-polkadot';
import { decodeAddress } from '@polkadot/keyring';
import { DecodedTx, RegisterDidWithCDDArgs, PreApproveAssetArgs, TxData, AddAndAffirmWithMediatorsArgs } from './iface';
import {
DecodedTx,
RegisterDidWithCDDArgs,
PreApproveAssetArgs,
TxData,
AddAndAffirmWithMediatorsArgs,
RejectInstructionBuilderArgs,
} from './iface';
import polyxUtils from './utils';

export class Transaction extends SubstrateTransaction {
Expand Down Expand Up @@ -63,6 +70,11 @@ export class Transaction extends SubstrateTransaction {
result.amount = sendTokenArgs.legs[0].fungible.amount.toString();
result.assetId = sendTokenArgs.legs[0].fungible.assetId;
result.memo = sendTokenArgs.instructionMemo;
} else if (this.type === TransactionType.RejectInstruction) {
const rejectInstructionArgs = txMethod as RejectInstructionBuilderArgs;
result.instructionId = rejectInstructionArgs.id as string;
result.portfolioDID = rejectInstructionArgs.portfolio.did as string;
result.amount = '0'; // Reject instruction does not transfer any value
} else {
return super.toJson() as TxData;
}
Expand All @@ -88,6 +100,8 @@ export class Transaction extends SubstrateTransaction {
this.decodeInputsAndOutputsForPreApproveAsset(decodedTx);
} else if (this.type === TransactionType.SendToken) {
this.decodeInputsAndOutputsForSendToken(decodedTx);
} else if (this.type === TransactionType.RejectInstruction) {
this.decodeInputsAndOutputsForRejectInstruction(decodedTx);
}
}

Expand Down Expand Up @@ -148,4 +162,22 @@ export class Transaction extends SubstrateTransaction {
coin: this._coinConfig.name,
});
}

private decodeInputsAndOutputsForRejectInstruction(decodedTx: DecodedTx) {
const txMethod = decodedTx.method.args as RejectInstructionBuilderArgs;
const portfolioDID = txMethod.portfolio.did;
const value = '0'; // Reject instruction does not transfer any value

this._inputs.push({
address: portfolioDID,
value,
coin: this._coinConfig.name,
});

this._outputs.push({
address: portfolioDID,
value,
coin: this._coinConfig.name,
});
}
}
21 changes: 21 additions & 0 deletions modules/sdk-coin-polyx/src/lib/txnSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ export const AddAndAffirmWithMediatorsTransactionSchema = joi.object({
mediators: joi.array().length(0).required(),
});

export const RejectInstructionTransactionSchema = joi.object({
id: joi.string().required(),
portfolio: joi
.object({
did: addressSchema.required(),
kind: joi
.object({
default: joi.valid(null),
})
.required(),
})
.required(),
numberOfAssets: joi
.object({
fungible: joi.number().required(),
nonFungible: joi.number().required(),
offChain: joi.number().required(),
})
.optional(),
});

// For standalone bondExtra transactions
export const BondExtraTransactionSchema = joi.object({
value: joi.string().required(),
Expand Down
6 changes: 6 additions & 0 deletions modules/sdk-coin-polyx/test/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export const rawTx = {
unsigned:
'0xb90225140000000004001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db83634200bc6f7ec808f361c1353ab9dc88c3cc54b98d9eb60fed9c063e67a40925b8ef6100780602887b358cf48989d0d9aa6c8d2840420f00000000000000000000000000041208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db836342000130000000000000000000000000000000000000000000000000000000000000000055000c007bdb6a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d639fe96f0dab5a96e118de830dc1f5d0105adeae3f3208ce95e8e03494456e191',
},
rejectInstruction: {
signed:
'0x49028400e8164bbe81964be28292d96f62c2ef6117d911638f38ae1b1bbe69df0b6df127004a08d9ccbb4b157c3df4b3e7a8bc602e6f5fab075a4bf36b29963a9a8926d16b280c2e514e534d42bcf3173372c619449c595d6183a9fbe5f9a8efe9c5b6100965034800250d14370000000000001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db83634200',
unsigned:
'0xac250d14370000000000001208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db8363420025014c007bdb6a00070000002ace05e703aa50b48c0ccccfc8b424f7aab9a1e2c424ed12e45d20b1e8ffd0d6ffecb4672251137ef38c9aca7c031e578ae13c376c1699fc9cc094c29e16c7df',
},
};

export const stakingTx = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import should from 'should';
import { RejectInstructionBuilder } from '../../../src/lib';
import { utils } from '../../../src';

import { accounts, rawTx, chainName, genesisHash, mockTssSignature } from '../../resources';
import { buildTestConfig } from './base';
import { testnetMaterial } from '../../../src/resources';

describe('Polyx Reject Instruction Builder - Testnet', () => {
let builder: RejectInstructionBuilder;

const sender = accounts.account1;
const instructionId = '14100';
const portfolioDID = '0x1208d7851e6698249aea40742701ee1ef6cdcced260a7c49c1cca1a9db836342';

beforeEach(() => {
const config = buildTestConfig();
builder = new RejectInstructionBuilder(config).material(utils.getMaterial(config.network.type));
});

describe('build rejectInstruction transaction', () => {
it('should build a rejectInstruction transaction', async () => {
builder
.instructionId(instructionId)
.portfolioDID(portfolioDID)
.sender({ address: sender.address })
.validity({ firstValid: 3933, maxDuration: 64 })
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
.fee({ amount: 0, type: 'tip' });
builder.addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex'));
const tx = await builder.build();
const txJson = tx.toJson();
should.deepEqual(txJson.instructionId, instructionId);
should.deepEqual(txJson.portfolioDID, portfolioDID);
should.deepEqual(txJson.sender, sender.address);
should.deepEqual(txJson.blockNumber, 3933);
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
should.deepEqual(txJson.genesisHash, genesisHash);
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
should.deepEqual(txJson.nonce, 200);
should.deepEqual(txJson.tip, 0);
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
should.deepEqual(txJson.chainName, testnetMaterial.chainName);
should.deepEqual(txJson.eraPeriod, 64);
});

it('should build an unsigned rejectInstruction transaction', async () => {
builder
.instructionId(instructionId)
.portfolioDID(portfolioDID)
.sender({ address: sender.address })
.validity({ firstValid: 3933, maxDuration: 64 })
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
.fee({ amount: 0, type: 'tip' });
const tx = await builder.build();
const txJson = tx.toJson();
should.deepEqual(txJson.instructionId, instructionId);
should.deepEqual(txJson.portfolioDID, portfolioDID);
should.deepEqual(txJson.sender, sender.address);
should.deepEqual(txJson.blockNumber, 3933);
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
should.deepEqual(txJson.genesisHash, genesisHash);
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
should.deepEqual(txJson.nonce, 200);
should.deepEqual(txJson.tip, 0);
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
should.deepEqual(txJson.chainName, chainName);
should.deepEqual(txJson.eraPeriod, 64);
});

it('should build from raw signed tx', async () => {
if (rawTx.rejectInstruction?.signed) {
builder.from(rawTx.rejectInstruction.signed);
builder
.validity({ firstValid: 3933, maxDuration: 64 })
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
const tx = await builder.build();
const txJson = tx.toJson();
should.exist(txJson.instructionId);
should.deepEqual(txJson.instructionId, instructionId);
should.exist(txJson.portfolioDID);
should.deepEqual(txJson.portfolioDID, portfolioDID);
should.deepEqual(txJson.blockNumber, 3933);
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
should.deepEqual(txJson.genesisHash, genesisHash);
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
should.deepEqual(txJson.chainName, chainName);
should.deepEqual(txJson.eraPeriod, 64);
}
});

it('should build from raw unsigned tx', async () => {
if (rawTx.rejectInstruction?.unsigned) {
builder.from(rawTx.rejectInstruction.unsigned);
builder
.validity({ firstValid: 3933, maxDuration: 64 })
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
.sender({ address: sender.address })
.addSignature({ pub: sender.publicKey }, Buffer.from(mockTssSignature, 'hex'));

const tx = await builder.build();
const txJson = tx.toJson();
should.exist(txJson.instructionId);
should.deepEqual(txJson.instructionId, instructionId);
should.exist(txJson.portfolioDID);
should.deepEqual(txJson.portfolioDID, portfolioDID);
should.deepEqual(txJson.sender, sender.address);
should.deepEqual(txJson.blockNumber, 3933);
should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d');
should.deepEqual(txJson.genesisHash, genesisHash);
should.deepEqual(txJson.specVersion, Number(testnetMaterial.specVersion));
should.deepEqual(txJson.eraPeriod, 64);
should.deepEqual(txJson.transactionVersion, Number(testnetMaterial.txVersion));
should.deepEqual(txJson.chainName, chainName);
}
});

it('should validate instruction ID is set', async () => {
builder
.portfolioDID(portfolioDID)
.sender({ address: sender.address })
.validity({ firstValid: 3933, maxDuration: 64 })
.referenceBlock('0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d')
.sequenceId({ name: 'Nonce', keyword: 'nonce', value: 200 })
.fee({ amount: 0, type: 'tip' });

await builder.build().should.be.rejected();
});
});
});
Loading