Skip to content

Commit eeeb6e5

Browse files
authored
Merge pull request #6589 from BitGo/coin-5042-bsc-msg-builder
feat(sdk-coin-bsc): add message builder factory
2 parents d9b8eea + c5bb08e commit eeeb6e5

File tree

10 files changed

+463
-0
lines changed

10 files changed

+463
-0
lines changed

.gitcommitscopes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
account-lib
22
sdk-coin-ada
3+
sdk-coin-bsc
34
sdk-coin-rune
45
sdk-coin-sol
56
sdk-coin-sui

modules/account-lib/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ const coinBuilderMap = {
323323
const coinMessageBuilderFactoryMap = {
324324
eth: Eth.MessageBuilderFactory,
325325
hteth: Eth.MessageBuilderFactory,
326+
bsc: Bsc.MessageBuilderFactory,
327+
tbsc: Bsc.MessageBuilderFactory,
326328
ada: Ada.MessageBuilderFactory,
327329
tada: Ada.MessageBuilderFactory,
328330
sol: Sol.MessageBuilderFactory,

modules/sdk-coin-bsc/src/bsc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export class Bsc extends AbstractEthLikeNewCoins {
3030
return true;
3131
}
3232

33+
/** @inheritDoc */
34+
supportsMessageSigning(): boolean {
35+
return true;
36+
}
37+
3338
/** inherited doc */
3439
getDefaultMultisigType(): MultisigType {
3540
return multisigTypes.tss;

modules/sdk-coin-bsc/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import * as Utils from './utils';
33
export { TransactionBuilder } from './transactionBuilder';
44
export { TransferBuilder } from './transferBuilder';
55
export { Transaction, KeyPair } from '@bitgo/sdk-coin-eth';
6+
export { MessageBuilderFactory } from './messages';
67
export { Utils };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { MessageBuilderFactory } from '@bitgo/abstract-eth';
2+
3+
export { MessageBuilderFactory };
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import 'should';
2+
import sinon from 'sinon';
3+
import { BroadcastableMessage, MessageStandardType, serializeSignatures } from '@bitgo/sdk-core';
4+
import { fixtures } from './fixtures';
5+
import { Eip191MessageBuilder, EIP191Message } from '@bitgo/abstract-eth';
6+
7+
describe('BSC EIP191 Message Builder', () => {
8+
const sandbox = sinon.createSandbox();
9+
10+
afterEach(() => {
11+
sandbox.restore();
12+
});
13+
14+
describe('constructor', () => {
15+
it('should initialize with BSC coin configuration', () => {
16+
const builder = new Eip191MessageBuilder(fixtures.coin);
17+
18+
builder.should.be.instanceof(Eip191MessageBuilder);
19+
// Verify it's using BSC coin config
20+
fixtures.coin.name.should.equal('tbsc');
21+
});
22+
});
23+
24+
describe('build method', () => {
25+
it('should build a valid EIP191 message for BSC', async () => {
26+
const builder = new Eip191MessageBuilder(fixtures.coin);
27+
28+
builder.setPayload(fixtures.messages.validMessage).setMetadata({ customData: 'BSC test data' });
29+
30+
const message = await builder.build();
31+
32+
message.should.be.instanceof(EIP191Message);
33+
message.getType().should.equal(MessageStandardType.EIP191);
34+
message.getPayload().should.equal(fixtures.messages.validMessage);
35+
message.getMetadata()!.should.have.property('customData', 'BSC test data');
36+
message.getMetadata()!.should.have.property('encoding', 'utf8');
37+
});
38+
39+
it('should throw an error when building without setting payload', async () => {
40+
const builder = new Eip191MessageBuilder(fixtures.coin);
41+
42+
await builder.build().should.be.rejectedWith('Message payload must be set before building the message');
43+
});
44+
45+
it('should include BSC signers when building a message', async () => {
46+
const builder = new Eip191MessageBuilder(fixtures.coin);
47+
48+
builder.setPayload(fixtures.messages.validMessage);
49+
builder.addSigner(fixtures.eip191.signer);
50+
51+
const message = await builder.build();
52+
53+
message.getSigners().should.containEql(fixtures.eip191.signer);
54+
});
55+
56+
it('should include signatures when building a BSC message', async () => {
57+
const builder = new Eip191MessageBuilder(fixtures.coin);
58+
59+
builder.setPayload(fixtures.messages.validMessage);
60+
builder.addSignature(fixtures.eip191.signature);
61+
62+
const message = await builder.build();
63+
64+
message.getSignatures().should.containEql(fixtures.eip191.signature);
65+
});
66+
67+
it('should override metadata.encoding with utf8 for BSC', async () => {
68+
const builder = new Eip191MessageBuilder(fixtures.coin);
69+
70+
builder.setPayload(fixtures.messages.validMessage);
71+
builder.setMetadata({ encoding: 'hex', customData: 'BSC test data', network: 'bsc' });
72+
73+
const message = await builder.build();
74+
75+
message.getMetadata()!.should.have.property('encoding', 'utf8');
76+
message.getMetadata()!.should.have.property('customData', 'BSC test data');
77+
message.getMetadata()!.should.have.property('network', 'bsc');
78+
});
79+
80+
it('should handle BSC-specific metadata fields', async () => {
81+
const builder = new Eip191MessageBuilder(fixtures.coin);
82+
83+
builder.setPayload(fixtures.messages.validMessage);
84+
builder.setMetadata({
85+
network: 'bsc',
86+
chainId: 97, // BSC testnet chain ID
87+
customData: 'BSC-specific data',
88+
});
89+
90+
const message = await builder.build();
91+
92+
message.getMetadata()!.should.have.property('network', 'bsc');
93+
message.getMetadata()!.should.have.property('chainId', 97);
94+
message.getMetadata()!.should.have.property('customData', 'BSC-specific data');
95+
});
96+
});
97+
98+
describe('fromBroadcastFormat method', () => {
99+
it('should reconstruct a BSC message from broadcast format', async () => {
100+
const builder = new Eip191MessageBuilder(fixtures.coin);
101+
102+
const broadcastMessage: BroadcastableMessage = {
103+
type: MessageStandardType.EIP191,
104+
payload: fixtures.messages.validMessage,
105+
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
106+
signers: [fixtures.eip191.signer],
107+
metadata: fixtures.eip191.metadata,
108+
};
109+
110+
const message = await builder.fromBroadcastFormat(broadcastMessage);
111+
112+
message.should.be.instanceof(EIP191Message);
113+
message.getType().should.equal(MessageStandardType.EIP191);
114+
message.getPayload().should.equal(fixtures.messages.validMessage);
115+
message.getSignatures().should.containEql(fixtures.eip191.signature);
116+
message.getSigners().should.containEql(fixtures.eip191.signer);
117+
message.getMetadata()!.should.have.property('encoding', 'utf8');
118+
message.getMetadata()!.should.have.property('customData', 'BSC test data');
119+
message.getMetadata()!.should.have.property('network', 'bsc');
120+
});
121+
122+
it('should handle broadcast format with empty metadata for BSC', async () => {
123+
const builder = new Eip191MessageBuilder(fixtures.coin);
124+
125+
const broadcastMessage: BroadcastableMessage = {
126+
type: MessageStandardType.EIP191,
127+
payload: fixtures.messages.validMessage,
128+
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
129+
signers: [fixtures.eip191.signer],
130+
metadata: {},
131+
};
132+
133+
const message = await builder.fromBroadcastFormat(broadcastMessage);
134+
135+
message.getMetadata()!.should.have.property('encoding', 'utf8');
136+
message.getPayload().should.equal(fixtures.messages.validMessage);
137+
});
138+
139+
it('should handle special characters in BSC broadcast format', async () => {
140+
const builder = new Eip191MessageBuilder(fixtures.coin);
141+
142+
const broadcastMessage: BroadcastableMessage = {
143+
type: MessageStandardType.EIP191,
144+
payload: fixtures.messages.specialCharsMessage,
145+
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
146+
signers: [fixtures.eip191.signer],
147+
metadata: { network: 'bsc' },
148+
};
149+
150+
const message = await builder.fromBroadcastFormat(broadcastMessage);
151+
152+
message.getPayload().should.equal(fixtures.messages.specialCharsMessage);
153+
message.getMetadata()!.should.have.property('network', 'bsc');
154+
});
155+
});
156+
157+
describe('BSC-specific scenarios', () => {
158+
it('should work with multiple signers for BSC multisig scenarios', async () => {
159+
const builder = new Eip191MessageBuilder(fixtures.coin);
160+
const secondSigner = '0x1234567890123456789012345678901234567890';
161+
162+
builder.setPayload(fixtures.messages.validMessage);
163+
builder.addSigner(fixtures.eip191.signer);
164+
builder.addSigner(secondSigner);
165+
166+
const message = await builder.build();
167+
168+
message.getSigners().should.containEql(fixtures.eip191.signer);
169+
message.getSigners().should.containEql(secondSigner);
170+
message.getSigners().length.should.equal(2);
171+
});
172+
173+
it('should preserve BSC chain-specific metadata throughout build process', async () => {
174+
const builder = new Eip191MessageBuilder(fixtures.coin);
175+
176+
builder.setPayload(fixtures.messages.validMessage);
177+
builder.setMetadata({
178+
network: 'bsc',
179+
chainId: 56, // BSC mainnet
180+
gasPrice: '5000000000', // 5 Gwei
181+
contractAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
182+
});
183+
184+
const message = await builder.build();
185+
186+
const metadata = message.getMetadata()!;
187+
metadata.should.have.property('network', 'bsc');
188+
metadata.should.have.property('chainId', 56);
189+
metadata.should.have.property('gasPrice', '5000000000');
190+
metadata.should.have.property('contractAddress', '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c');
191+
});
192+
});
193+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { coins } from '@bitgo/statics';
2+
3+
// Test fixtures for BSC EIP-191 message tests
4+
export const fixtures = {
5+
coin: coins.get('tbsc'), // Using testnet BSC for tests
6+
messages: {
7+
validMessage: 'Hello, BSC world!',
8+
emptyMessage: '',
9+
specialCharsMessage: '!@#$%^&*()',
10+
longMessage:
11+
'This is a very long message that contains multiple lines and special characters. ' +
12+
'It is designed to test the EIP-191 message format with a more complex payload on BSC network.',
13+
},
14+
eip191: {
15+
validSignablePayload:
16+
'0x19457468657265756d205369676e6564204d6573736167653a1048656c6c6f2c20425343twentieth776f726c6421',
17+
signature: {
18+
publicKey: { pub: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf' },
19+
signature: Buffer.from(
20+
'5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b',
21+
'hex'
22+
),
23+
},
24+
signer: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
25+
metadata: {
26+
encoding: 'utf8',
27+
customData: 'BSC test data',
28+
network: 'bsc',
29+
},
30+
},
31+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './fixtures';
2+
export * from './messageBuilderFactory';
3+
export * from './eip191MessageBuilder';
4+
export * from './integration';
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'should';
2+
import { MessageStandardType } from '@bitgo/sdk-core';
3+
import { coins } from '@bitgo/statics';
4+
import { MessageBuilderFactory } from '../../../src';
5+
import { fixtures } from './fixtures';
6+
7+
describe('BSC Message Builder Integration Tests', () => {
8+
describe('End-to-end message building workflow', () => {
9+
it('should complete full message lifecycle from factory to broadcast format', async () => {
10+
// Create factory with BSC coin configuration
11+
const factory = new MessageBuilderFactory(fixtures.coin);
12+
13+
// Get EIP-191 builder
14+
const builder = factory.getMessageBuilder(MessageStandardType.EIP191);
15+
16+
// Build message with BSC-specific metadata
17+
builder
18+
.setPayload('Integration test message for BSC')
19+
.setMetadata({
20+
network: 'bsc',
21+
chainId: 97,
22+
timestamp: Date.now(),
23+
dapp: 'BitGo BSC Integration',
24+
})
25+
.addSigner(fixtures.eip191.signer)
26+
.addSignature(fixtures.eip191.signature);
27+
28+
const message = await builder.build();
29+
30+
// Verify message properties
31+
message.getType().should.equal(MessageStandardType.EIP191);
32+
message.getPayload().should.equal('Integration test message for BSC');
33+
message.getSigners().should.containEql(fixtures.eip191.signer);
34+
message.getSignatures().should.containEql(fixtures.eip191.signature);
35+
36+
// Verify BSC-specific metadata
37+
const metadata = message.getMetadata()!;
38+
metadata.should.have.property('network', 'bsc');
39+
metadata.should.have.property('chainId', 97);
40+
metadata.should.have.property('dapp', 'BitGo BSC Integration');
41+
metadata.should.have.property('encoding', 'utf8');
42+
43+
// Convert to broadcast format
44+
const broadcastFormat = await message.toBroadcastFormat();
45+
broadcastFormat.should.have.property('type', MessageStandardType.EIP191);
46+
broadcastFormat.should.have.property('payload', 'Integration test message for BSC');
47+
broadcastFormat.should.have.property('signers');
48+
broadcastFormat.should.have.property('serializedSignatures');
49+
broadcastFormat.should.have.property('metadata');
50+
51+
// Reconstruct from broadcast format
52+
const rebuiltMessage = await builder.fromBroadcastFormat(broadcastFormat);
53+
rebuiltMessage.getPayload().should.equal(message.getPayload());
54+
rebuiltMessage.getType().should.equal(message.getType());
55+
rebuiltMessage.getSigners().should.deepEqual(message.getSigners());
56+
});
57+
58+
it('should work with production BSC coin configuration', async () => {
59+
const bscCoin = coins.get('bsc'); // Production BSC
60+
const factory = new MessageBuilderFactory(bscCoin);
61+
62+
const builder = factory.getMessageBuilder(MessageStandardType.EIP191);
63+
builder.setPayload('Production BSC message test');
64+
65+
const message = await builder.build();
66+
67+
message.getType().should.equal(MessageStandardType.EIP191);
68+
message.getPayload().should.equal('Production BSC message test');
69+
70+
// Verify it uses production BSC configuration
71+
bscCoin.name.should.equal('bsc');
72+
bscCoin.network.family.should.equal('bsc');
73+
});
74+
75+
it('should handle complex BSC transaction metadata', async () => {
76+
const factory = new MessageBuilderFactory(fixtures.coin);
77+
const builder = factory.getMessageBuilder(MessageStandardType.EIP191);
78+
79+
const complexMetadata = {
80+
network: 'bsc',
81+
chainId: 56,
82+
transactionType: 'token_transfer',
83+
tokenContract: '0x55d398326f99059fF775485246999027B3197955', // USDT on BSC
84+
amount: '1000000000000000000', // 1 token with 18 decimals
85+
recipient: '0x742d35Cc6634C0532925a3b8D431C2FE1e05dB2b',
86+
gasLimit: '21000',
87+
gasPrice: '5000000000',
88+
nonce: 42,
89+
dappUrl: 'https://pancakeswap.finance',
90+
userAgent: 'BitGo SDK BSC Integration Test',
91+
};
92+
93+
builder
94+
.setPayload('Complex BSC transaction approval')
95+
.setMetadata(complexMetadata)
96+
.addSigner(fixtures.eip191.signer);
97+
98+
const message = await builder.build();
99+
const metadata = message.getMetadata()!;
100+
101+
// Verify all complex metadata is preserved
102+
metadata.should.have.property('tokenContract', '0x55d398326f99059fF775485246999027B3197955');
103+
metadata.should.have.property('amount', '1000000000000000000');
104+
metadata.should.have.property('recipient', '0x742d35Cc6634C0532925a3b8D431C2FE1e05dB2b');
105+
metadata.should.have.property('gasLimit', '21000');
106+
metadata.should.have.property('nonce', 42);
107+
metadata.should.have.property('dappUrl', 'https://pancakeswap.finance');
108+
metadata.should.have.property('encoding', 'utf8'); // Should be overridden
109+
});
110+
});
111+
});

0 commit comments

Comments
 (0)