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
9 changes: 7 additions & 2 deletions modules/sdk-core/src/bitgo/safe/safes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
* @experimental The safe client surface is experimental and may change (including breaking
* changes) before the public release.
*/
import { InitializeSafeBody, SafeData } from '@bitgo/public-types';
import { BitGoBase } from '../bitgoBase';
import { decodeWithCodec } from '../utils/codecs';
import { postWithCodec } from '../utils/postWithCodec';
import { FinalizeSafeOptions, InitializeSafeOptions } from './iSafe';
import { CreateSafeOptions, GetSafeOptions, ISafes, ListSafesOptions, SafeCreationHandle, SafeKeys } from './iSafes';
import { Safe } from './safe';
Expand Down Expand Up @@ -43,10 +46,12 @@ export class Safes implements ISafes {

/**
* Phase 1 — initialize a safe (metadata only, no key material).
* Implemented in WCN-1192 Phase 2 (blocked on WCN-1175).
* POST /api/v2/enterprise/:eId/safes { label }
*/
async initializeSafe(params: InitializeSafeOptions): Promise<Safe> {
throw new Error('Safes.initializeSafe is not yet implemented (WCN-1192 Phase 2)');
const response = await postWithCodec(this.bitgo, this.url(), InitializeSafeBody, params).result();
const safeData = decodeWithCodec(SafeData, response, 'SafeData');
return new Safe(this.bitgo, safeData);
}

/**
Expand Down
36 changes: 28 additions & 8 deletions modules/sdk-core/test/unit/bitgo/safe/safes.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import * as sinon from 'sinon';
import 'should';
import { Enterprise, Safes } from '../../../../src';
import { Enterprise, Safe, Safes } from '../../../../src';

describe('Safes', function () {
let safes: Safes;
let mockBitGo: any;
const safeDataWire = {
id: 'test-safe-id',
enterpriseId: 'test-enterprise-id',
label: 'my safe',
status: 'active',
creator: 'creator-id',
users: [{ userId: 'creator-id', permissions: ['admin', 'spend'] }],
createdAt: '2026-07-07T00:00:00.000Z',
};

beforeEach(function () {
mockBitGo = {
url: sinon.stub().callsFake((path: string) => path),
post: sinon.stub(),
};
safes = new Safes(mockBitGo, 'test-enterprise-id');
});
Expand All @@ -17,18 +27,28 @@ describe('Safes', function () {
sinon.restore();
});

// Phase 2 (generateSafe/initialize/createSafeKeys/finalize) and Phase 3 (list/get) are not yet
// implemented — they are gated on WCN-1175 / WCN-1176 / WCN-1177. Assert the interface is wired
// and stubbed for now.
describe('initializeSafe', function () {
it('POSTs to the safes collection URL and returns a Safe', async function () {
const send = sinon.stub().returns({ result: sinon.stub().resolves(safeDataWire) });
mockBitGo.post.returns({ send });

const result = await safes.initializeSafe({ label: 'my safe' });

result.should.be.instanceof(Safe);
result.id().should.equal('test-safe-id');
result.enterpriseId().should.equal('test-enterprise-id');
sinon.assert.calledWith(mockBitGo.post, '/enterprise/test-enterprise-id/safes');
sinon.assert.calledWith(send, { label: 'my safe' });
});
});

// generateSafe/createSafeKeys/finalize and list/get are not yet implemented — gated on
// WCN-1175 / WCN-1176 / WCN-1177.
describe('unimplemented lifecycle methods', function () {
it('generateSafe throws (Phase 2)', async function () {
await safes.generateSafe({ label: 'v', passphrase: 'p' }).should.be.rejectedWith(/not yet implemented .*Phase 2/);
});

it('initializeSafe throws (Phase 2)', async function () {
await safes.initializeSafe({ label: 'v' }).should.be.rejectedWith(/not yet implemented .*Phase 2/);
});

it('createSafeKeys throws (Phase 2)', async function () {
await safes
.createSafeKeys({ label: 'v', passphrase: 'p', safeId: 'vid' })
Expand Down
Loading