Skip to content
Draft
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
78 changes: 78 additions & 0 deletions modules/bitgo/test/v2/unit/unspents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import nock = require('nock');
import * as sinon from 'sinon';
import * as assert from 'assert';
import { common, Wallet } from '@bitgo/sdk-core';
import { TestBitGo } from '@bitgo/sdk-test';
import { BitGo } from '../../../src';
Expand Down Expand Up @@ -201,4 +202,81 @@ describe('Verify string type is used for value of unspent', function () {
deleteScope.done();
});
});

describe('Frozen Unspents', function () {
after(nock.cleanAll);

const unspentId = 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123:0';

it('should list frozen unspents by passing frozen=true query param', async function () {
const scope = nock(bgUrl)
.get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/unspents`)
.query({ frozen: 'true' })
.reply(200, { unspents: [], count: 0 });

await wallet.unspents({ frozen: true });

scope.done();
});

it('should list non-frozen unspents by passing frozen=false query param', async function () {
const scope = nock(bgUrl)
.get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/unspents`)
.query({ frozen: 'false' })
.reply(200, { unspents: [], count: 0 });

await wallet.unspents({ frozen: false });

scope.done();
});

it('should not include frozen param when not specified', async function () {
const scope = nock(bgUrl)
.get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/unspents`)
.query({})
.reply(200, { unspents: [], count: 0 });

await wallet.unspents({});

scope.done();
});

it('should freeze an unspent', async function () {
const scope = nock(bgUrl)
.post(
`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/unspents/${encodeURIComponent(unspentId)}/freeze`
)
.reply(200, { id: unspentId, frozen: true });

await wallet.freezeUnspent({ unspentId });

scope.done();
});

it('should unfreeze an unspent', async function () {
const scope = nock(bgUrl)
.delete(
`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/unspents/${encodeURIComponent(unspentId)}/freeze`
)
.reply(200, { id: unspentId, frozen: false });

await wallet.unfreezeUnspent({ unspentId });

scope.done();
});

it('should throw when freezeUnspent is called without unspentId', async function () {
await assert.rejects(
() => wallet.freezeUnspent({ unspentId: '' }),
{ message: 'unspentId is required' }
);
});

it('should throw when unfreezeUnspent is called without unspentId', async function () {
await assert.rejects(
() => wallet.unfreezeUnspent({ unspentId: '' }),
{ message: 'unspentId is required' }
);
});
});
});
11 changes: 11 additions & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,15 @@ export interface UnspentsOptions extends PaginationOptions {
segwit?: boolean;
chains?: number[];
unspentIds?: string[];
frozen?: boolean;
}

export interface FreezeUnspentOptions {
unspentId: string;
}

export interface UnfreezeUnspentOptions {
unspentId: string;
}

export interface ManageUnspentReservationOptions {
Expand Down Expand Up @@ -1141,6 +1150,8 @@ export interface IWallet {
transferBySequenceId(params?: TransferBySequenceIdOptions): Promise<any>;
maximumSpendable(params?: MaximumSpendableOptions): Promise<MaximumSpendable>;
unspents(params?: UnspentsOptions): Promise<any>;
freezeUnspent(params: FreezeUnspentOptions): Promise<any>;
unfreezeUnspent(params: UnfreezeUnspentOptions): Promise<any>;
consolidateUnspents(params?: ConsolidateUnspentsOptions): Promise<unknown>;
fanoutUnspents(params?: FanoutUnspentsOptions): Promise<unknown>;
updateTokenFlushThresholds(thresholds?: any): Promise<any>;
Expand Down
29 changes: 29 additions & 0 deletions modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ import {
ForwarderBalance,
ForwarderBalanceOptions,
FreezeOptions,
FreezeUnspentOptions,
UnfreezeUnspentOptions,
FundForwarderParams,
FundForwardersOptions,
GetAddressOptions,
Expand Down Expand Up @@ -742,6 +744,7 @@ export class Wallet implements IWallet {
async unspents(params: UnspentsOptions = {}): Promise<any> {
const query = _.pick(params, [
'chains',
'frozen',
'limit',
'maxValue',
'minConfirms',
Expand All @@ -756,6 +759,32 @@ export class Wallet implements IWallet {
return this.bitgo.get(this.url('/unspents')).query(query).result();
}

/**
* Freeze a specific unspent, preventing it from being selected during transaction building.
* @param params
* @param params.unspentId - the ID of the unspent to freeze (format: txid:vout)
* @returns {*}
*/
async freezeUnspent(params: FreezeUnspentOptions): Promise<any> {
if (!params.unspentId) {
throw new Error('unspentId is required');
}
return this.bitgo.post(this.url(`/unspents/${encodeURIComponent(params.unspentId)}/freeze`)).result();
}

/**
* Unfreeze a previously frozen unspent, allowing it to be selected during transaction building.
* @param params
* @param params.unspentId - the ID of the unspent to unfreeze (format: txid:vout)
* @returns {*}
*/
async unfreezeUnspent(params: UnfreezeUnspentOptions): Promise<any> {
if (!params.unspentId) {
throw new Error('unspentId is required');
}
return this.bitgo.del(this.url(`/unspents/${encodeURIComponent(params.unspentId)}/freeze`)).result();
}

/**
* Consolidate or fanout unspents on a wallet
*
Expand Down
Loading