Skip to content
14 changes: 5 additions & 9 deletions modules/sdk-core/src/bitgo/trading/network/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,16 @@ export class TradingNetwork implements ITradingNetwork {
*/
async prepareAllocation({
walletPassphrase,
clientExternalId = uuidV4(),
nonce = crypto.randomBytes(32).toString('hex'),
...body
}: PrepareNetworkAllocationParams): Promise<CreateNetworkAllocationParams> {
if (!body.clientExternalId) {
body.clientExternalId = uuidV4();
}
if (!body.nonce) {
body.nonce = crypto.randomBytes(32).toString('hex');
}

const payload = JSON.stringify(body);
const allocation = { ...body, clientExternalId, nonce };
const payload = JSON.stringify(allocation);
const signature = await this.wallet.toTradingAccount().signPayload({ payload, walletPassphrase });

return {
...body,
...allocation,
payload,
signature,
};
Expand Down
5 changes: 4 additions & 1 deletion modules/sdk-core/src/bitgo/trading/network/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ export type GetNetworkAllocationByIdResponse = {
allocation: NetworkAllocation;
};

export type PrepareNetworkAllocationParams = Omit<CreateNetworkAllocationParams, 'payload' | 'signature'> & {
export type PrepareNetworkAllocationParams = Omit<
CreateNetworkAllocationParams,
'payload' | 'signature' | 'clientExternalId' | 'nonce'
> & {
walletPassphrase?: string;
clientExternalId?: string;
nonce?: string;
Expand Down
150 changes: 150 additions & 0 deletions modules/sdk-core/test/unit/bitgo/trading/network/network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @prettier
*/
import assert from 'assert';
import sinon from 'sinon';
import { TradingNetwork } from '../../../../../src';

describe('TradingNetwork', function () {
const enterpriseId = 'enterprise-id';
const walletId = 'wallet-id';
const signature = 'signature';
let mockBitGo: any;
let mockWallet: any;
let requestCalls: Array<{
method: string;
url: string;
headers: Record<string, string>;
body: unknown;
}>;
let tradingNetwork: TradingNetwork;
let tradingAccount: { signPayload: sinon.SinonStub };

beforeEach(function () {
requestCalls = [];
tradingAccount = { signPayload: sinon.stub().resolves(signature) };
mockWallet = {
id: sinon.stub().returns(walletId),
toTradingAccount: sinon.stub().returns(tradingAccount),
};

mockBitGo = {
microservicesUrl: sinon.stub().callsFake((path: string) => `https://microservices.example${path}`),
};

for (const method of ['get', 'post', 'put']) {
mockBitGo[method] = sinon.stub().callsFake((url: string) => {
const call: { method: string; url: string; headers: Record<string, string>; body: unknown } = {
method,
url,
headers: {},
body: undefined,
};
requestCalls.push(call);
const request = {
set: sinon.stub().callsFake((name: string, value: string) => {
call.headers[name] = value;
return request;
}),
send: sinon.stub().callsFake((body: unknown) => {
call.body = body;
return request;
}),
result: sinon.stub().resolves({ ok: true }),
};
return request;
});
}

tradingNetwork = new TradingNetwork(enterpriseId, mockWallet, mockBitGo);
});

it('uses enterprise-scoped URLs and headers for network reads', async function () {
await tradingNetwork.getBalances({ pageNumber: 2 });
await tradingNetwork.getSettlementById({ settlementId: 'settlement-id' });

assert.deepStrictEqual(requestCalls, [
{
method: 'get',
url: 'https://microservices.example/api/network/v1/enterprises/enterprise-id/clients/balances',
headers: { 'enterprise-id': enterpriseId },
body: { pageNumber: 2 },
},
{
method: 'get',
url: 'https://microservices.example/api/network/v1/enterprises/enterprise-id/clients/settlements/settlement-id',
headers: { 'enterprise-id': enterpriseId },
body: {},
},
]);
});

it('prepares a signed allocation with generated identifiers', async function () {
const prepared = await tradingNetwork.prepareAllocation({
connectionId: 'connection-id',
amount: { currency: 'tbtc' as string, quantity: '100' },
notes: 'test',
});

assert.strictEqual(prepared.connectionId, 'connection-id');
assert.strictEqual(prepared.signature, signature);
assert.match(prepared.clientExternalId, /^[0-9a-f-]{36}$/);
assert.match(prepared.nonce, /^[0-9a-f]{64}$/);
assert.strictEqual(
prepared.payload,
JSON.stringify({
connectionId: 'connection-id',
amount: { currency: 'tbtc', quantity: '100' },
notes: 'test',
clientExternalId: prepared.clientExternalId,
nonce: prepared.nonce,
})
);
tradingAccount.signPayload.calledOnce.should.be.true();
tradingAccount.signPayload.firstCall.args[0].should.deepEqual({
payload: prepared.payload,
walletPassphrase: undefined,
});
});

it('submits allocation and deallocation payloads to connection-scoped endpoints', async function () {
const params = {
connectionId: 'connection-id',
payload: '{}',
signature,
amount: { currency: 'tbtc', quantity: '100' },
clientExternalId: 'external-id',
nonce: 'nonce',
};

await tradingNetwork.createAllocation(params);
await tradingNetwork.createDeallocation(params);

assert.deepStrictEqual(requestCalls, [
{
method: 'post',
url: 'https://microservices.example/api/network/v1/enterprises/enterprise-id/clients/connections/connection-id/allocations',
headers: { 'enterprise-id': enterpriseId },
body: {
payload: params.payload,
signature: params.signature,
amount: params.amount,
clientExternalId: params.clientExternalId,
nonce: params.nonce,
},
},
{
method: 'post',
url: 'https://microservices.example/api/network/v1/enterprises/enterprise-id/clients/connections/connection-id/deallocations',
headers: { 'enterprise-id': enterpriseId },
body: {
payload: params.payload,
signature: params.signature,
amount: params.amount,
clientExternalId: params.clientExternalId,
nonce: params.nonce,
},
},
]);
});
});
Loading