Skip to content

Commit 9440aa6

Browse files
authored
Merge pull request #8323 from BitGo/CAAS-1008-add-go-account-signing-script
chore: add go account signing script
2 parents 779c40f + be8a12a commit 9440aa6

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* Sign a pre-built Go Account transaction payload (Step 2 of 3)
3+
*
4+
* Use this script when the transaction payload has already been built (Step 1)
5+
* and you only need to produce a signature to send back in Step 3.
6+
*
7+
* This is useful when the build step happens in a separate process or service
8+
* (e.g. your backend calls the BitGo build API, then forwards the payload here
9+
* for local signing in a secure environment).
10+
*
11+
* Step 1: Build — done externally, paste the payload string below
12+
* Step 2: Sign — this script → outputs a hex-encoded signature
13+
* Step 3: Submit — use the signature + payload to call submitTransaction()
14+
*
15+
* The user key is decrypted locally — your passphrase is NEVER sent over
16+
* the network.
17+
*
18+
* For a single script that runs all three steps, see go-account-withdrawal.ts.
19+
*
20+
* Required environment variables (in examples/.env):
21+
* TESTNET_ACCESS_TOKEN - your BitGo access token
22+
* OFC_WALLET_ID - the wallet ID of your Go Account
23+
* OFC_WALLET_PASSPHRASE - the passphrase used when the wallet was created
24+
*
25+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
26+
*/
27+
28+
import { BitGoAPI } from '@bitgo/sdk-api';
29+
import { coins } from 'bitgo';
30+
require('dotenv').config({ path: '../../../.env' });
31+
32+
// Initialize BitGo SDK
33+
const bitgo = new BitGoAPI({
34+
accessToken: process.env.TESTNET_ACCESS_TOKEN,
35+
env: 'test', // Change to 'production' for mainnet
36+
});
37+
38+
// Go Accounts use the 'ofc' (Off-Chain) coin
39+
const coin = 'ofc';
40+
bitgo.register(coin, coins.Ofc.createInstance);
41+
42+
// ---------------------------------------------------------------------------
43+
// Configuration — update these values or set them as environment variables
44+
// ---------------------------------------------------------------------------
45+
46+
/** The wallet ID of your Go Account */
47+
const walletId = process.env.OFC_WALLET_ID || 'your_wallet_id';
48+
49+
/** Passphrase used to encrypt the wallet user key when the wallet was created */
50+
const walletPassphrase = process.env.OFC_WALLET_PASSPHRASE || 'your_wallet_passphrase';
51+
52+
/**
53+
* The payload string returned by Step 1 (prebuildTransaction or the build API).
54+
*/
55+
const prebuildPayload = process.env.OFC_PREBUILD_PAYLOAD || 'your_payload';
56+
57+
// ---------------------------------------------------------------------------
58+
59+
async function main() {
60+
console.log('=== Go Account: Sign Transaction Payload (Step 2 of 3) ===\n');
61+
62+
// Validate the payload is present and is parseable JSON.
63+
// JSON.parse + JSON.stringify normalizes escaping so the string sent to the
64+
// finalize endpoint is guaranteed to be valid JSON.
65+
if (!prebuildPayload) {
66+
throw new Error(
67+
'OFC_PREBUILD_PAYLOAD environment variable is required.\n' +
68+
'Set it to the JSON string returned by the prebuild step (Step 1).\n' +
69+
'Tip: run go-account-withdrawal.ts to see the prebuild result.'
70+
);
71+
}
72+
let normalizedPayload: string;
73+
try {
74+
normalizedPayload = JSON.stringify(JSON.parse(prebuildPayload));
75+
} catch {
76+
throw new Error(
77+
'OFC_PREBUILD_PAYLOAD is not valid JSON.\n' +
78+
'Do not hardcode it as a JS string literal — pass it via the environment variable\n' +
79+
'using the raw JSON string from the prebuild API response.'
80+
);
81+
}
82+
83+
console.log(`Fetching wallet ${walletId}...`);
84+
const wallet = await bitgo.coin(coin).wallets().get({ id: walletId });
85+
console.log(`✓ Wallet: ${wallet.label()} (${wallet.id()})\n`);
86+
87+
// -------------------------------------------------------------------------
88+
// Sign the payload
89+
//
90+
// tradingAccount.signPayload:
91+
// 1. Fetches the encrypted user key from BitGo
92+
// 2. Decrypts it locally using walletPassphrase
93+
// 3. Signs the payload using Bitcoin message signing (secp256k1)
94+
// 4. Returns a hex-encoded 65-byte recoverable signature
95+
//
96+
// Your passphrase is NEVER sent over the network.
97+
// -------------------------------------------------------------------------
98+
console.log('Signing payload...');
99+
const tradingAccount = wallet.toTradingAccount();
100+
101+
const signature = await tradingAccount.signPayload({
102+
payload: normalizedPayload,
103+
walletPassphrase,
104+
});
105+
106+
console.log('✓ Payload signed successfully\n');
107+
console.log('='.repeat(60));
108+
console.log('Payload (pass to Step 3):');
109+
console.log(normalizedPayload);
110+
console.log('\nSignature (hex, pass to Step 3):');
111+
console.log(signature);
112+
console.log('='.repeat(60));
113+
114+
console.log('\nNext step — submit the signed transaction:');
115+
console.log(' await wallet.submitTransaction({');
116+
console.log(' halfSigned: { payload, txHex: signature },');
117+
console.log(' });');
118+
}
119+
120+
main().catch((e) => {
121+
console.error('\n❌ Error signing transaction payload:', e);
122+
process.exit(1);
123+
});

0 commit comments

Comments
 (0)