Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitcommitscopes
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
account-lib
sdk-coin-ada
sdk-coin-bsc
sdk-coin-rune
sdk-coin-sol
sdk-coin-sui
Expand Down
2 changes: 2 additions & 0 deletions modules/account-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ const coinBuilderMap = {
const coinMessageBuilderFactoryMap = {
eth: Eth.MessageBuilderFactory,
hteth: Eth.MessageBuilderFactory,
bsc: Bsc.MessageBuilderFactory,
tbsc: Bsc.MessageBuilderFactory,
ada: Ada.MessageBuilderFactory,
tada: Ada.MessageBuilderFactory,
sol: Sol.MessageBuilderFactory,
Expand Down
5 changes: 5 additions & 0 deletions modules/sdk-coin-bsc/src/bsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export class Bsc extends AbstractEthLikeNewCoins {
return true;
}

/** @inheritDoc */
supportsMessageSigning(): boolean {
return true;
}

/** inherited doc */
getDefaultMultisigType(): MultisigType {
return multisigTypes.tss;
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-bsc/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import * as Utils from './utils';
export { TransactionBuilder } from './transactionBuilder';
export { TransferBuilder } from './transferBuilder';
export { Transaction, KeyPair } from '@bitgo/sdk-coin-eth';
export { MessageBuilderFactory } from './messages';
export { Utils };
3 changes: 3 additions & 0 deletions modules/sdk-coin-bsc/src/lib/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { MessageBuilderFactory } from '@bitgo/abstract-eth';

export { MessageBuilderFactory };
193 changes: 193 additions & 0 deletions modules/sdk-coin-bsc/test/unit/messages/eip191MessageBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import 'should';
import sinon from 'sinon';
import { BroadcastableMessage, MessageStandardType, serializeSignatures } from '@bitgo/sdk-core';
import { fixtures } from './fixtures';
import { Eip191MessageBuilder, EIP191Message } from '@bitgo/abstract-eth';

describe('BSC EIP191 Message Builder', () => {
const sandbox = sinon.createSandbox();

afterEach(() => {
sandbox.restore();
});

describe('constructor', () => {
it('should initialize with BSC coin configuration', () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.should.be.instanceof(Eip191MessageBuilder);
// Verify it's using BSC coin config
fixtures.coin.name.should.equal('tbsc');
});
});

describe('build method', () => {
it('should build a valid EIP191 message for BSC', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage).setMetadata({ customData: 'BSC test data' });

const message = await builder.build();

message.should.be.instanceof(EIP191Message);
message.getType().should.equal(MessageStandardType.EIP191);
message.getPayload().should.equal(fixtures.messages.validMessage);
message.getMetadata()!.should.have.property('customData', 'BSC test data');
message.getMetadata()!.should.have.property('encoding', 'utf8');
});

it('should throw an error when building without setting payload', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

await builder.build().should.be.rejectedWith('Message payload must be set before building the message');
});

it('should include BSC signers when building a message', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage);
builder.addSigner(fixtures.eip191.signer);

const message = await builder.build();

message.getSigners().should.containEql(fixtures.eip191.signer);
});

it('should include signatures when building a BSC message', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage);
builder.addSignature(fixtures.eip191.signature);

const message = await builder.build();

message.getSignatures().should.containEql(fixtures.eip191.signature);
});

it('should override metadata.encoding with utf8 for BSC', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage);
builder.setMetadata({ encoding: 'hex', customData: 'BSC test data', network: 'bsc' });

const message = await builder.build();

message.getMetadata()!.should.have.property('encoding', 'utf8');
message.getMetadata()!.should.have.property('customData', 'BSC test data');
message.getMetadata()!.should.have.property('network', 'bsc');
});

it('should handle BSC-specific metadata fields', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage);
builder.setMetadata({
network: 'bsc',
chainId: 97, // BSC testnet chain ID
customData: 'BSC-specific data',
});

const message = await builder.build();

message.getMetadata()!.should.have.property('network', 'bsc');
message.getMetadata()!.should.have.property('chainId', 97);
message.getMetadata()!.should.have.property('customData', 'BSC-specific data');
});
});

describe('fromBroadcastFormat method', () => {
it('should reconstruct a BSC message from broadcast format', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

const broadcastMessage: BroadcastableMessage = {
type: MessageStandardType.EIP191,
payload: fixtures.messages.validMessage,
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
signers: [fixtures.eip191.signer],
metadata: fixtures.eip191.metadata,
};

const message = await builder.fromBroadcastFormat(broadcastMessage);

message.should.be.instanceof(EIP191Message);
message.getType().should.equal(MessageStandardType.EIP191);
message.getPayload().should.equal(fixtures.messages.validMessage);
message.getSignatures().should.containEql(fixtures.eip191.signature);
message.getSigners().should.containEql(fixtures.eip191.signer);
message.getMetadata()!.should.have.property('encoding', 'utf8');
message.getMetadata()!.should.have.property('customData', 'BSC test data');
message.getMetadata()!.should.have.property('network', 'bsc');
});

it('should handle broadcast format with empty metadata for BSC', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

const broadcastMessage: BroadcastableMessage = {
type: MessageStandardType.EIP191,
payload: fixtures.messages.validMessage,
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
signers: [fixtures.eip191.signer],
metadata: {},
};

const message = await builder.fromBroadcastFormat(broadcastMessage);

message.getMetadata()!.should.have.property('encoding', 'utf8');
message.getPayload().should.equal(fixtures.messages.validMessage);
});

it('should handle special characters in BSC broadcast format', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

const broadcastMessage: BroadcastableMessage = {
type: MessageStandardType.EIP191,
payload: fixtures.messages.specialCharsMessage,
serializedSignatures: serializeSignatures([fixtures.eip191.signature]),
signers: [fixtures.eip191.signer],
metadata: { network: 'bsc' },
};

const message = await builder.fromBroadcastFormat(broadcastMessage);

message.getPayload().should.equal(fixtures.messages.specialCharsMessage);
message.getMetadata()!.should.have.property('network', 'bsc');
});
});

describe('BSC-specific scenarios', () => {
it('should work with multiple signers for BSC multisig scenarios', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);
const secondSigner = '0x1234567890123456789012345678901234567890';

builder.setPayload(fixtures.messages.validMessage);
builder.addSigner(fixtures.eip191.signer);
builder.addSigner(secondSigner);

const message = await builder.build();

message.getSigners().should.containEql(fixtures.eip191.signer);
message.getSigners().should.containEql(secondSigner);
message.getSigners().length.should.equal(2);
});

it('should preserve BSC chain-specific metadata throughout build process', async () => {
const builder = new Eip191MessageBuilder(fixtures.coin);

builder.setPayload(fixtures.messages.validMessage);
builder.setMetadata({
network: 'bsc',
chainId: 56, // BSC mainnet
gasPrice: '5000000000', // 5 Gwei
contractAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
});

const message = await builder.build();

const metadata = message.getMetadata()!;
metadata.should.have.property('network', 'bsc');
metadata.should.have.property('chainId', 56);
metadata.should.have.property('gasPrice', '5000000000');
metadata.should.have.property('contractAddress', '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c');
});
});
});
31 changes: 31 additions & 0 deletions modules/sdk-coin-bsc/test/unit/messages/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { coins } from '@bitgo/statics';

// Test fixtures for BSC EIP-191 message tests
export const fixtures = {
coin: coins.get('tbsc'), // Using testnet BSC for tests
messages: {
validMessage: 'Hello, BSC world!',
emptyMessage: '',
specialCharsMessage: '!@#$%^&*()',
longMessage:
'This is a very long message that contains multiple lines and special characters. ' +
'It is designed to test the EIP-191 message format with a more complex payload on BSC network.',
},
eip191: {
validSignablePayload:
'0x19457468657265756d205369676e6564204d6573736167653a1048656c6c6f2c20425343twentieth776f726c6421',
signature: {
publicKey: { pub: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf' },
signature: Buffer.from(
'5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b',
'hex'
),
},
signer: '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
metadata: {
encoding: 'utf8',
customData: 'BSC test data',
network: 'bsc',
},
},
};
4 changes: 4 additions & 0 deletions modules/sdk-coin-bsc/test/unit/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './fixtures';
export * from './messageBuilderFactory';
export * from './eip191MessageBuilder';
export * from './integration';
111 changes: 111 additions & 0 deletions modules/sdk-coin-bsc/test/unit/messages/integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'should';
import { MessageStandardType } from '@bitgo/sdk-core';
import { coins } from '@bitgo/statics';
import { MessageBuilderFactory } from '../../../src';
import { fixtures } from './fixtures';

describe('BSC Message Builder Integration Tests', () => {
describe('End-to-end message building workflow', () => {
it('should complete full message lifecycle from factory to broadcast format', async () => {
// Create factory with BSC coin configuration
const factory = new MessageBuilderFactory(fixtures.coin);

// Get EIP-191 builder
const builder = factory.getMessageBuilder(MessageStandardType.EIP191);

// Build message with BSC-specific metadata
builder
.setPayload('Integration test message for BSC')
.setMetadata({
network: 'bsc',
chainId: 97,
timestamp: Date.now(),
dapp: 'BitGo BSC Integration',
})
.addSigner(fixtures.eip191.signer)
.addSignature(fixtures.eip191.signature);

const message = await builder.build();

// Verify message properties
message.getType().should.equal(MessageStandardType.EIP191);
message.getPayload().should.equal('Integration test message for BSC');
message.getSigners().should.containEql(fixtures.eip191.signer);
message.getSignatures().should.containEql(fixtures.eip191.signature);

// Verify BSC-specific metadata
const metadata = message.getMetadata()!;
metadata.should.have.property('network', 'bsc');
metadata.should.have.property('chainId', 97);
metadata.should.have.property('dapp', 'BitGo BSC Integration');
metadata.should.have.property('encoding', 'utf8');

// Convert to broadcast format
const broadcastFormat = await message.toBroadcastFormat();
broadcastFormat.should.have.property('type', MessageStandardType.EIP191);
broadcastFormat.should.have.property('payload', 'Integration test message for BSC');
broadcastFormat.should.have.property('signers');
broadcastFormat.should.have.property('serializedSignatures');
broadcastFormat.should.have.property('metadata');

// Reconstruct from broadcast format
const rebuiltMessage = await builder.fromBroadcastFormat(broadcastFormat);
rebuiltMessage.getPayload().should.equal(message.getPayload());
rebuiltMessage.getType().should.equal(message.getType());
rebuiltMessage.getSigners().should.deepEqual(message.getSigners());
});

it('should work with production BSC coin configuration', async () => {
const bscCoin = coins.get('bsc'); // Production BSC
const factory = new MessageBuilderFactory(bscCoin);

const builder = factory.getMessageBuilder(MessageStandardType.EIP191);
builder.setPayload('Production BSC message test');

const message = await builder.build();

message.getType().should.equal(MessageStandardType.EIP191);
message.getPayload().should.equal('Production BSC message test');

// Verify it uses production BSC configuration
bscCoin.name.should.equal('bsc');
bscCoin.network.family.should.equal('bsc');
});

it('should handle complex BSC transaction metadata', async () => {
const factory = new MessageBuilderFactory(fixtures.coin);
const builder = factory.getMessageBuilder(MessageStandardType.EIP191);

const complexMetadata = {
network: 'bsc',
chainId: 56,
transactionType: 'token_transfer',
tokenContract: '0x55d398326f99059fF775485246999027B3197955', // USDT on BSC
amount: '1000000000000000000', // 1 token with 18 decimals
recipient: '0x742d35Cc6634C0532925a3b8D431C2FE1e05dB2b',
gasLimit: '21000',
gasPrice: '5000000000',
nonce: 42,
dappUrl: 'https://pancakeswap.finance',
userAgent: 'BitGo SDK BSC Integration Test',
};

builder
.setPayload('Complex BSC transaction approval')
.setMetadata(complexMetadata)
.addSigner(fixtures.eip191.signer);

const message = await builder.build();
const metadata = message.getMetadata()!;

// Verify all complex metadata is preserved
metadata.should.have.property('tokenContract', '0x55d398326f99059fF775485246999027B3197955');
metadata.should.have.property('amount', '1000000000000000000');
metadata.should.have.property('recipient', '0x742d35Cc6634C0532925a3b8D431C2FE1e05dB2b');
metadata.should.have.property('gasLimit', '21000');
metadata.should.have.property('nonce', 42);
metadata.should.have.property('dappUrl', 'https://pancakeswap.finance');
metadata.should.have.property('encoding', 'utf8'); // Should be overridden
});
});
});
Loading