Skip to content

Commit e4f3b4c

Browse files
committed
2 parents 362b0f6 + 8b0b018 commit e4f3b4c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4695
-66
lines changed

commitlint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ module.exports = {
6565
'COIN-',
6666
'FIAT-',
6767
'ME-',
68+
'ANT-',
6869
'#', // Prefix used by GitHub issues
6970
],
7071
},

examples/ts/create-go-account.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Create a Go Account wallet at BitGo.
3+
* This makes use of the convenience function generateWallet with type: 'trading'
4+
*
5+
* IMPORTANT: You must backup the encrypted private key and encrypted wallet passphrase!
6+
*
7+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
8+
*/
9+
10+
import { BitGoAPI } from '@bitgo/sdk-api';
11+
import { coins } from 'bitgo';
12+
require('dotenv').config({ path: '../../.env' });
13+
14+
const bitgo = new BitGoAPI({
15+
accessToken: process.env.TESTNET_ACCESS_TOKEN,
16+
env: 'test', // Change this to env: 'production' when you are ready for production
17+
});
18+
19+
// Go Accounts use the 'ofc' (Off-Chain) coin
20+
const coin = 'ofc';
21+
bitgo.register(coin, coins.Ofc.createInstance);
22+
23+
// TODO: set a label for your new Go Account here
24+
const label = 'Example Go Account Wallet';
25+
26+
// TODO: set your passphrase for your new wallet here (encrypts the private key)
27+
const passphrase = 'go_account_wallet_passphrase';
28+
29+
// TODO: set your passcode encryption code here (encrypts the passphrase itself)
30+
const passcodeEncryptionCode = 'encryption_code_for_passphrase';
31+
32+
// TODO: set your enterprise ID for your new wallet here
33+
const enterprise = 'your_enterprise_id';
34+
35+
async function main() {
36+
const response = await bitgo.coin(coin).wallets().generateWallet({
37+
label,
38+
passphrase,
39+
passcodeEncryptionCode,
40+
enterprise,
41+
type: 'trading', // Required for Go Accounts
42+
});
43+
44+
// Type guard to ensure we got a Go Account response
45+
if (!('userKeychain' in response)) {
46+
throw new Error('Go account missing required user keychain');
47+
}
48+
49+
const { wallet, userKeychain, encryptedWalletPassphrase } = response;
50+
51+
console.log(`Wallet ID: ${wallet.id()}`);
52+
53+
console.log('BACKUP THE FOLLOWING INFORMATION: ');
54+
console.log('User Keychain:');
55+
console.log(`Keychain ID: ${userKeychain.id}`);
56+
console.log(`Public Key: ${userKeychain.pub}`);
57+
console.log(`Encrypted Private Key: ${userKeychain.encryptedPrv}`);
58+
59+
console.log(`Encrypted Wallet Passphrase: ${encryptedWalletPassphrase}`);
60+
61+
// Create receive address for Go Account
62+
const receiveAddress = await wallet.createAddress();
63+
console.log('Go Account Receive Address:', receiveAddress.address);
64+
}
65+
66+
main().catch((e) => console.error('Error creating Go Account:', e));
67+

examples/ts/share-wallet.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ bitgo.register(coin, Tbtc.createInstance);
2222
const walletId = '';
2323

2424
// TODO: set BitGo account email of wallet share recipient
25-
const recipient = null;
25+
const recipient = "recipient_email";
2626

2727
// TODO: set share permissions as a comma-separated list
2828
// Valid permissions to choose from are: view, spend, manage, admin
2929
const perms = 'view';
3030

3131
// TODO: provide the passphrase for the wallet being shared
32-
const passphrase = null;
32+
const passphrase = "passhrase";
3333

3434
async function main() {
3535
const wallet = await bitgo.coin(coin).wallets().get({ id: walletId });

modules/account-lib/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@ const coinMessageBuilderFactoryMap = {
331331
tada: Ada.MessageBuilderFactory,
332332
sol: Sol.MessageBuilderFactory,
333333
tsol: Sol.MessageBuilderFactory,
334+
arbeth: Eth.MessageBuilderFactory,
335+
tarbeth: Eth.MessageBuilderFactory,
336+
opeth: Eth.MessageBuilderFactory,
337+
topeth: Eth.MessageBuilderFactory,
334338
};
335339

336340
coins

modules/bitgo/test/v2/unit/wallets.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,51 @@ describe('V2 Wallets:', function () {
515515
params.passphrase
516516
);
517517
});
518+
519+
it('should generate Go Account wallet', async () => {
520+
const ofcWallets = bitgo.coin('ofc').wallets();
521+
522+
const params: GenerateWalletOptions = {
523+
label: 'Go Account Wallet',
524+
passphrase: 'go_account_password',
525+
enterprise: 'enterprise-id',
526+
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
527+
type: 'trading',
528+
};
529+
530+
const keychainId = 'user_keychain_id';
531+
532+
// Mock keychain creation and upload
533+
nock(bgUrl)
534+
.post('/api/v2/ofc/key', function (body) {
535+
body.should.have.property('encryptedPrv');
536+
body.should.have.property('originalPasscodeEncryptionCode');
537+
body.keyType.should.equal('independent');
538+
body.source.should.equal('user');
539+
return true;
540+
})
541+
.reply(200, { id: keychainId, pub: 'userPub', encryptedPrv: 'encryptedPrivateKey' });
542+
543+
// Mock wallet creation
544+
const walletNock = nock(bgUrl)
545+
.post('/api/v2/ofc/wallet/add', function (body) {
546+
body.type.should.equal('trading');
547+
body.m.should.equal(1);
548+
body.n.should.equal(1);
549+
body.keys.should.have.length(1);
550+
body.keys[0].should.equal(keychainId);
551+
return true;
552+
})
553+
.reply(200, { id: 'wallet123', keys: [keychainId] });
554+
555+
const response = await ofcWallets.generateWallet(params);
556+
557+
walletNock.isDone().should.be.true();
558+
559+
assert.ok(response.encryptedWalletPassphrase);
560+
assert.ok(response.wallet);
561+
assert.ok('userKeychain' in response);
562+
});
518563
});
519564

520565
describe('Generate TSS wallet:', function () {

modules/express/src/clientRoutes.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -687,11 +687,11 @@ export function handleV2CreateLocalKeyChain(req: ExpressApiRouteRequest<'express
687687
* handle wallet share
688688
* @param req
689689
*/
690-
async function handleV2ShareWallet(req: express.Request) {
690+
export async function handleV2ShareWallet(req: ExpressApiRouteRequest<'express.v2.wallet.share', 'post'>) {
691691
const bitgo = req.bitgo;
692-
const coin = bitgo.coin(req.params.coin);
693-
const wallet = await coin.wallets().get({ id: req.params.id });
694-
return wallet.shareWallet(req.body);
692+
const coin = bitgo.coin(req.decoded.coin);
693+
const wallet = await coin.wallets().get({ id: req.decoded.id });
694+
return wallet.shareWallet(req.decoded);
695695
}
696696

697697
/**
@@ -1621,8 +1621,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16211621

16221622
router.post('express.v2.wallet.createAddress', [prepareBitGo(config), typedPromiseWrapper(handleV2CreateAddress)]);
16231623

1624-
// share wallet
1625-
app.post('/api/v2/:coin/wallet/:id/share', parseBody, prepareBitGo(config), promiseWrapper(handleV2ShareWallet));
1624+
router.post('express.v2.wallet.share', [prepareBitGo(config), typedPromiseWrapper(handleV2ShareWallet)]);
16261625
app.post(
16271626
'/api/v2/:coin/walletshare/:id/acceptshare',
16281627
parseBody,

modules/express/src/typedRoutes/api/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { PostWalletRecoverToken } from './v2/walletRecoverToken';
2929
import { PostCoinSignTx } from './v2/coinSignTx';
3030
import { PostWalletSignTx } from './v2/walletSignTx';
3131
import { PostWalletTxSignTSS } from './v2/walletTxSignTSS';
32+
import { PostShareWallet } from './v2/shareWallet';
3233
import { PutExpressWalletUpdate } from './v2/expressWalletUpdate';
3334

3435
export const ExpressApi = apiSpec({
@@ -113,6 +114,9 @@ export const ExpressApi = apiSpec({
113114
'express.v2.wallet.signtxtss': {
114115
post: PostWalletTxSignTSS,
115116
},
117+
'express.v2.wallet.share': {
118+
post: PostShareWallet,
119+
},
116120
'express.wallet.update': {
117121
put: PutExpressWalletUpdate,
118122
},
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
import { ShareState, ShareWalletKeychain } from '../../schemas/wallet';
5+
6+
/**
7+
* Path parameters for sharing a wallet
8+
*/
9+
export const ShareWalletParams = {
10+
/** Coin ticker / chain identifier */
11+
coin: t.string,
12+
/** Wallet ID */
13+
id: t.string,
14+
} as const;
15+
16+
/**
17+
* Request body for sharing a wallet
18+
*/
19+
export const ShareWalletBody = {
20+
/** Recipient email address */
21+
email: t.string,
22+
/** Permissions string, e.g., "view,spend" */
23+
permissions: t.string,
24+
/** Wallet passphrase used to derive shared key when needed */
25+
walletPassphrase: optional(t.string),
26+
/** Optional message to include with the share */
27+
message: optional(t.string),
28+
/** If true, allows sharing without a keychain */
29+
reshare: optional(t.boolean),
30+
/** If true, skips sharing the wallet keychain with the recipient */
31+
skipKeychain: optional(t.boolean),
32+
/** If true, suppresses email notification to the recipient */
33+
disableEmail: optional(t.boolean),
34+
} as const;
35+
36+
/**
37+
* Response for sharing a wallet
38+
*/
39+
export const ShareWalletResponse200 = t.intersection([
40+
t.type({
41+
/** Wallet share id */
42+
id: t.string,
43+
/** Coin of the wallet */
44+
coin: t.string,
45+
/** Wallet id */
46+
wallet: t.string,
47+
/** Id of the sharer */
48+
fromUser: t.string,
49+
/** Id of the recipient */
50+
toUser: t.string,
51+
/** Comma-separated list of privileges for wallet */
52+
permissions: t.string,
53+
}),
54+
t.partial({
55+
/** Wallet label */
56+
walletLabel: t.string,
57+
/** User-readable message */
58+
message: t.string,
59+
/** Share state */
60+
state: ShareState,
61+
/** Enterprise id, if applicable */
62+
enterprise: t.string,
63+
/** Pending approval id, if one was generated */
64+
pendingApprovalId: t.string,
65+
/** Included if shared with spend permission */
66+
keychain: ShareWalletKeychain,
67+
}),
68+
]);
69+
70+
export const ShareWalletResponse = {
71+
200: ShareWalletResponse200,
72+
400: BitgoExpressError,
73+
} as const;
74+
75+
/**
76+
* Share this wallet with another BitGo user.
77+
*
78+
* @operationId express.v2.wallet.share
79+
*/
80+
export const PostShareWallet = httpRoute({
81+
path: '/api/v2/{coin}/wallet/{id}/share',
82+
method: 'POST',
83+
request: httpRequest({
84+
params: ShareWalletParams,
85+
body: ShareWalletBody,
86+
}),
87+
response: ShareWalletResponse,
88+
});

modules/express/src/typedRoutes/schemas/wallet.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
import * as t from 'io-ts';
22

3+
export const ShareState = t.union([
4+
t.literal('pendingapproval'),
5+
t.literal('active'),
6+
t.literal('accepted'),
7+
t.literal('canceled'),
8+
t.literal('rejected'),
9+
]);
10+
11+
export const ShareWalletKeychain = t.partial({
12+
pub: t.string,
13+
encryptedPrv: t.string,
14+
fromPubKey: t.string,
15+
toPubKey: t.string,
16+
path: t.string,
17+
});
18+
319
/**
420
* Wallet user with permissions
521
*/

0 commit comments

Comments
 (0)