Skip to content

Commit b9f9f72

Browse files
authored
Merge pull request #6371 from BitGo/coin-4593-build-and-send-message-scripts
chore: build and sign message sample scripts
2 parents 9fa7425 + b4fa6f8 commit b9f9f72

File tree

8 files changed

+279
-64
lines changed

8 files changed

+279
-64
lines changed

examples/ts/build-message.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Pre-build a message from the wallet
3+
*
4+
* This tool will help you see how to use the BitGo API to easily build
5+
* a message from a wallet.
6+
*
7+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
8+
*/
9+
10+
import {BitGoAPI} from '@bitgo/sdk-api';
11+
import {Hteth} from "@bitgo/sdk-coin-eth";
12+
import {MessageStandardType} from "@bitgo/sdk-core"; // Replace with your given coin (e.g. Ltc, Tltc)
13+
require('dotenv').config({ path: '../../.env' });
14+
15+
const bitgo = new BitGoAPI({
16+
accessToken: process.env.TESTNET_ACCESS_TOKEN,
17+
env: 'test', // Change this to env: 'production' when you are ready for production
18+
});
19+
20+
// Set the coin name to match the blockchain and network
21+
// doge = dogecoin, tdoge = testnet dogecoin
22+
const coin = 'hteth';
23+
bitgo.register(coin, Hteth.createInstance);
24+
25+
const id = '';
26+
27+
async function main() {
28+
const wallet = await bitgo.coin(coin).wallets().get({ id });
29+
console.log(`Wallet label: ${wallet.label()}`);
30+
31+
const txRequest = await wallet.buildSignMessageRequest({
32+
message: {
33+
messageRaw: 'Hello, BitGo!',
34+
messageStandardType: MessageStandardType.EIP191,
35+
},
36+
});
37+
console.dir(txRequest);
38+
}
39+
40+
main().catch((e) => console.log(e));

examples/ts/sign-message.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Sign a Message from an MPC wallet at BitGo.
3+
*
4+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
5+
*/
6+
import { BitGo } from 'bitgo';
7+
import { MessageStandardType } from '@bitgo/sdk-core';
8+
9+
const bitgo = new BitGo({ env: 'test' });
10+
11+
const coin = 'hteth';
12+
const basecoin = bitgo.coin(coin);
13+
const accessToken = '';
14+
const walletId = '';
15+
const walletPassphrase = '';
16+
17+
async function signMessage(): Promise<void> {
18+
await bitgo.authenticateWithAccessToken({ accessToken });
19+
const walletInstance = await basecoin.wallets().get({ id: walletId });
20+
21+
const messageTxn = await walletInstance.signMessage({
22+
message: {
23+
messageRaw: 'Hello BitGo!',
24+
messageStandardType: MessageStandardType.EIP191,
25+
},
26+
walletPassphrase,
27+
});
28+
29+
console.log(messageTxn);
30+
}
31+
32+
signMessage().catch(console.error);

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

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,34 @@ import * as nock from 'nock';
99
import * as _ from 'lodash';
1010

1111
import {
12+
BaseTssUtils,
1213
common,
1314
CustomSigningFunction,
15+
Ecdsa,
1416
ECDSAUtils,
1517
EDDSAUtils,
18+
GetUserPrvOptions,
19+
Keychains,
20+
KeyType,
21+
ManageUnspentsOptions,
22+
MessageStandardType,
23+
MessageTypes,
24+
PopulatedIntent,
25+
PrebuildTransactionWithIntentOptions,
1626
RequestTracer,
27+
SendManyOptions,
28+
SignatureShareType,
29+
SignedMessage,
30+
SignTypedDataVersion,
1731
TokenType,
1832
TssUtils,
1933
TxRequest,
20-
Wallet,
21-
SignatureShareType,
22-
Ecdsa,
23-
Keychains,
34+
TxRequestVersion,
2435
TypedData,
2536
TypedMessage,
26-
MessageTypes,
27-
SignTypedDataVersion,
28-
GetUserPrvOptions,
29-
ManageUnspentsOptions,
30-
SignedMessage,
31-
BaseTssUtils,
32-
KeyType,
33-
SendManyOptions,
34-
PopulatedIntent,
35-
TxRequestVersion,
37+
Wallet,
3638
WalletSignMessageOptions,
3739
WalletSignTypedDataOptions,
38-
PrebuildTransactionWithIntentOptions,
3940
} from '@bitgo/sdk-core';
4041

4142
import { TestBitGo } from '@bitgo/sdk-test';
@@ -3414,6 +3415,10 @@ describe('V2 Wallet:', function () {
34143415

34153416
describe('Message Signing', function () {
34163417
const txHash = '0xrrrsss1b';
3418+
const messageRaw = 'hello world';
3419+
const messageEncoded = Buffer.from(`\u0019Ethereum Signed Message:\n${messageRaw.length}${messageRaw}`).toString(
3420+
'hex'
3421+
);
34173422
const txRequestForMessageSigning: TxRequest = {
34183423
txRequestId: reqId.toString(),
34193424
transactions: [],
@@ -3438,19 +3443,19 @@ describe('V2 Wallet:', function () {
34383443
signatureShares: [{ from: SignatureShareType.USER, to: SignatureShareType.USER, share: '' }],
34393444
combineSigShare: '0:rrr:sss:3',
34403445
txHash,
3446+
messageEncoded,
34413447
},
34423448
],
34433449
};
34443450
let signTxRequestForMessage;
34453451
const messageSigningCoins = ['teth', 'tpolygon'];
3446-
const messageRaw = 'test';
34473452
const expected: SignedMessage = {
34483453
txRequestId: reqId.toString(),
34493454
txHash,
34503455
signature: txHash,
34513456
messageRaw,
34523457
coin: 'teth',
3453-
messageEncoded: Buffer.from('\u0019Ethereum Signed Message:\n4test').toString('hex'),
3458+
messageEncoded,
34543459
};
34553460

34563461
beforeEach(async function () {
@@ -3467,14 +3472,27 @@ describe('V2 Wallet:', function () {
34673472
nock.cleanAll();
34683473
});
34693474

3470-
it('should throw error for unsupported coins', async function () {
3471-
await tssSolWallet
3472-
.signMessage({
3473-
reqId,
3474-
message: { messageRaw },
3475-
prv: 'secretKey',
3476-
})
3477-
.should.be.rejectedWith('Message signing not supported for Testnet Solana');
3475+
describe('should throw error for unsupported coins', function () {
3476+
it('sol signMessage', async function () {
3477+
await tssSolWallet
3478+
.signMessage({
3479+
reqId,
3480+
message: { messageRaw },
3481+
prv: 'secretKey',
3482+
})
3483+
.should.be.rejectedWith('Message signing not supported for Testnet Solana');
3484+
});
3485+
3486+
it('sol create signMessage tx request', async function () {
3487+
await tssSolWallet
3488+
.buildSignMessageRequest({
3489+
message: {
3490+
messageRaw,
3491+
messageStandardType: MessageStandardType.EIP191,
3492+
},
3493+
})
3494+
.should.be.rejectedWith('Message signing not supported for Testnet Solana');
3495+
});
34783496
});
34793497

34803498
messageSigningCoins.map((coinName) => {
@@ -3483,7 +3501,19 @@ describe('V2 Wallet:', function () {
34833501
tssEthWallet = new Wallet(bitgo, bitgo.coin(coinName), ethWalletData);
34843502
const txRequestId = txRequestForMessageSigning.txRequestId;
34853503

3486-
it('should sign message', async function () {
3504+
it('should create tx Request with signMessage intent', async function () {
3505+
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
3506+
3507+
const txRequest = await tssEthWallet.buildSignMessageRequest({
3508+
message: {
3509+
messageRaw,
3510+
messageStandardType: MessageStandardType.EIP191,
3511+
},
3512+
});
3513+
txRequest.should.deepEqual(txRequestForMessageSigning);
3514+
});
3515+
3516+
it(`[${coinName}] should sign message`, async function () {
34873517
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
34883518
nock(bgUrl)
34893519
.get(
@@ -3495,7 +3525,7 @@ describe('V2 Wallet:', function () {
34953525

34963526
const signMessage = await tssEthWallet.signMessage({
34973527
reqId,
3498-
message: { messageRaw, txRequestId },
3528+
message: { messageRaw, txRequestId, messageStandardType: MessageStandardType.EIP191 },
34993529
prv: 'secretKey',
35003530
});
35013531
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3505,14 +3535,14 @@ describe('V2 Wallet:', function () {
35053535
);
35063536
});
35073537

3508-
it('should sign message when custodianMessageId is provided', async function () {
3538+
it(`[${coinName}] should sign message when custodianMessageId is provided`, async function () {
35093539
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
3510-
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/txrequests`).reply(200, txRequestForMessageSigning);
3540+
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
35113541

35123542
const signMessage = await tssEthWallet.signMessage({
35133543
custodianMessageId: 'unittest',
35143544
reqId,
3515-
message: { messageRaw },
3545+
message: { messageRaw, messageStandardType: MessageStandardType.EIP191 },
35163546
prv: 'secretKey',
35173547
});
35183548
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3522,13 +3552,13 @@ describe('V2 Wallet:', function () {
35223552
);
35233553
});
35243554

3525-
it('should sign message when txRequestId not provided', async function () {
3555+
it(`[${coinName}] should sign message when txRequestId not provided`, async function () {
35263556
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
3527-
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/txrequests`).reply(200, txRequestForMessageSigning);
3557+
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
35283558

35293559
const signMessage = await tssEthWallet.signMessage({
35303560
reqId,
3531-
message: { messageRaw },
3561+
message: { messageRaw, messageStandardType: MessageStandardType.EIP191 },
35323562
prv: 'secretKey',
35333563
});
35343564
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3542,7 +3572,7 @@ describe('V2 Wallet:', function () {
35423572
await tssEthWallet
35433573
.signMessage({
35443574
reqId,
3545-
message: { messageRaw, txRequestId },
3575+
message: { messageRaw, txRequestId, messageStandardType: MessageStandardType.EIP191 },
35463576
prv: '',
35473577
})
35483578
.should.be.rejectedWith('keychain does not have property encryptedPrv');

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export interface SignedMessage {
419419
signature: string;
420420
messageRaw: string;
421421
messageEncoded?: string;
422+
messageStandardType?: MessageStandardType;
422423
txRequestId: string;
423424
}
424425

0 commit comments

Comments
 (0)