diff --git a/modules/sdk-core/src/bitgo/safe/safes.ts b/modules/sdk-core/src/bitgo/safe/safes.ts index 230359bd94..6a67548d43 100644 --- a/modules/sdk-core/src/bitgo/safe/safes.ts +++ b/modules/sdk-core/src/bitgo/safe/safes.ts @@ -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'; @@ -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 { - 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); } /** diff --git a/modules/sdk-core/test/unit/bitgo/safe/safes.ts b/modules/sdk-core/test/unit/bitgo/safe/safes.ts index 813f6fb149..ebab857957 100644 --- a/modules/sdk-core/test/unit/bitgo/safe/safes.ts +++ b/modules/sdk-core/test/unit/bitgo/safe/safes.ts @@ -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'); }); @@ -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' })