Skip to content

Commit bc5c71c

Browse files
committed
feat(account-lib): register message builder factory
TICKET: COIN-4592
1 parent 1a3c85e commit bc5c71c

File tree

11 files changed

+181
-16
lines changed

11 files changed

+181
-16
lines changed

.gitcommitscopes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
account-lib
12
sdk-coin-rune
23
sdk-coin-sui
34
sdk-core

modules/abstract-eth/src/lib/messages/messageBuilderFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Eip191MessageBuilder } from './eip191/eip191MessageBuilder';
1+
import { Eip191MessageBuilder } from './eip191';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { BaseMessageBuilderFactory, IMessageBuilder, MessageStandardType } from '@bitgo/sdk-core';
44

modules/account-lib/src/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
Eddsa,
88
accountLibBaseCoin,
99
acountLibCrypto,
10+
BaseMessageBuilderFactory,
11+
BuildMessageError,
1012
} from '@bitgo/sdk-core';
1113
import { BaseCoin as CoinConfig, CoinFeature, coins } from '@bitgo/statics';
1214
export { Ed25519BIP32, Eddsa };
@@ -311,6 +313,11 @@ const coinBuilderMap = {
311313
tvet: Vet.TransactionBuilderFactory,
312314
};
313315

316+
const coinMessageBuilderFactoryMap = {
317+
eth: Eth.MessageBuilderFactory,
318+
hteth: Eth.MessageBuilderFactory,
319+
};
320+
314321
coins
315322
.filter((coin) => coin.features.includes(CoinFeature.SHARED_EVM_SDK))
316323
.forEach((coin) => {
@@ -337,6 +344,14 @@ export function getBuilder(coinName: string): BaseBuilder {
337344
return new builderClass(coins.get(coinName));
338345
}
339346

347+
export function getMessageBuilderFactory(coinName: string): BaseMessageBuilderFactory {
348+
const messageBuilderFactoryClass = coinMessageBuilderFactoryMap[coinName];
349+
if (!messageBuilderFactoryClass) {
350+
throw new BuildMessageError(`Message builder factory for coin ${coinName} not supported`);
351+
}
352+
return new messageBuilderFactoryClass(coins.get(coinName));
353+
}
354+
340355
/**
341356
* Register a new coin instance with its builder factory
342357
*
@@ -354,3 +369,20 @@ export function register<T extends BaseTransactionBuilderFactory>(
354369
coinBuilderMap[coinName] = builderFactory; // For now register the constructor function until reimplement getBuilder method
355370
return factory;
356371
}
372+
373+
/**
374+
* Register a new coin instance with its message builder factory constructor.
375+
*
376+
* @param {string} coinName coin name as it was registered in @bitgo/statics
377+
* @param {any} messageBuilderFactory the message builder factory class for that coin
378+
* @returns {any} the message builder factory instance for the registered coin
379+
*/
380+
export function registerMessageBuilderFactory<T extends BaseMessageBuilderFactory>(
381+
coinName: string,
382+
messageBuilderFactory: { new (_coinConfig: Readonly<CoinConfig>): T },
383+
): T {
384+
const coinConfig = coins.get(coinName);
385+
const factory = new messageBuilderFactory(coinConfig);
386+
coinMessageBuilderFactoryMap[coinName] = messageBuilderFactory;
387+
return factory;
388+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { BaseCoin } from '@bitgo/statics';
2+
import {
3+
BaseMessage,
4+
BaseMessageBuilder,
5+
BaseMessageBuilderFactory,
6+
IMessage,
7+
IMessageBuilder,
8+
MessageOptions,
9+
MessageStandardType,
10+
} from '@bitgo/sdk-core';
11+
12+
export class MockMessageBuilderFactory extends BaseMessageBuilderFactory {
13+
constructor(coinConfig: Readonly<BaseCoin>) {
14+
super(coinConfig);
15+
}
16+
17+
getMessageBuilder(type: MessageStandardType): IMessageBuilder {
18+
return new MockMessageBuilder(this.coinConfig, type);
19+
}
20+
}
21+
22+
export class MockMessageBuilder extends BaseMessageBuilder {
23+
constructor(coinConfig: Readonly<BaseCoin>, type: MessageStandardType = MessageStandardType.UNKNOWN) {
24+
super(coinConfig, type);
25+
}
26+
27+
async build(): Promise<IMessage> {
28+
return new MockMessage({
29+
coinConfig: this.coinConfig,
30+
payload: this.payload,
31+
type: this.type,
32+
signatures: this.signatures,
33+
signers: this.signers,
34+
metadata: {
35+
...this.metadata,
36+
},
37+
});
38+
}
39+
40+
async fromBroadcastFormat(broadcastMessage: any): Promise<IMessage> {
41+
this.setType(broadcastMessage.type);
42+
this.setPayload(broadcastMessage.payload);
43+
this.setSignatures(broadcastMessage.signatures || []);
44+
this.setSigners(broadcastMessage.signers || []);
45+
if (broadcastMessage.metadata) {
46+
this.setMetadata(broadcastMessage.metadata);
47+
}
48+
return this.build();
49+
}
50+
}
51+
52+
export class MockMessage extends BaseMessage {
53+
constructor(options: MessageOptions) {
54+
super(options);
55+
}
56+
57+
async getSignablePayload(): Promise<string | Buffer> {
58+
if (this.signablePayload) {
59+
return this.signablePayload;
60+
}
61+
return Buffer.from(this.payload);
62+
}
63+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import assert from 'assert';
2+
import should from 'should';
3+
import sinon from 'sinon';
4+
import { getMessageBuilderFactory, registerMessageBuilderFactory } from '../../src';
5+
import { BaseMessageBuilderFactory, BuildMessageError } from '@bitgo/sdk-core';
6+
import { coins, BaseCoin } from '@bitgo/statics';
7+
import { MockMessageBuilderFactory } from './fixtures';
8+
9+
describe('Message Builder Factory', () => {
10+
describe('getMessageBuilderFactory', () => {
11+
it('should fail to instantiate an unsupported coin', () => {
12+
assert.throws(
13+
() => getMessageBuilderFactory('fakeUnsupported'),
14+
(e: Error) => {
15+
return (
16+
e instanceof BuildMessageError &&
17+
e.message === 'Message builder factory for coin fakeUnsupported not supported'
18+
);
19+
},
20+
);
21+
});
22+
23+
it('should succeed for supported coins', () => {
24+
const ethFactory = getMessageBuilderFactory('eth');
25+
should.exist(ethFactory);
26+
ethFactory.should.be.instanceof(BaseMessageBuilderFactory);
27+
28+
// Verify hteth is also supported
29+
const htethFactory = getMessageBuilderFactory('hteth');
30+
should.exist(htethFactory);
31+
htethFactory.should.be.instanceof(BaseMessageBuilderFactory);
32+
});
33+
});
34+
35+
describe('registerMessageBuilderFactory', () => {
36+
// Mock message builder that implements required abstract methods
37+
38+
it('should register a new message builder factory', () => {
39+
const coinName = 'fakeTestCoin';
40+
const mockCoin = sinon.createStubInstance(BaseCoin);
41+
sinon
42+
.stub(coins, 'get')
43+
.withArgs(coinName)
44+
.returns(mockCoin as unknown as BaseCoin);
45+
46+
const factory = registerMessageBuilderFactory(coinName, MockMessageBuilderFactory);
47+
should.exist(factory);
48+
factory.should.be.instanceof(MockMessageBuilderFactory);
49+
50+
// Verify we can get it back
51+
const retrievedFactory = getMessageBuilderFactory(coinName);
52+
should.exist(retrievedFactory);
53+
retrievedFactory.should.be.instanceof(MockMessageBuilderFactory);
54+
55+
// Restore the stub
56+
sinon.restore();
57+
});
58+
});
59+
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
4343
import type * as EthTxLib from '@ethereumjs/tx';
4444
import { BigNumber } from 'bignumber.js';
4545

46-
import { TransactionBuilder } from './lib/transactionBuilder';
46+
import { TransactionBuilder } from './lib';
4747
import { Erc20Token } from './erc20Token';
4848

4949
export {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from '@bitgo/abstract-eth';
44
// exporting Ethereum TransactionBuilder and TransferBuilder
55
export { TransactionBuilder } from './transactionBuilder';
66
export { TransferBuilder } from './transferBuilder';
7+
export * from './messages';
78

89
// for Backwards Compatibility
910
import { Interface, Utils } from '@bitgo/abstract-eth';
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 };

modules/sdk-core/src/account-lib/baseCoin/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export class BuildTransactionError extends ExtendableError {
3232
}
3333
}
3434

35+
export class BuildMessageError extends ExtendableError {
36+
constructor(message: string) {
37+
super(message);
38+
}
39+
}
40+
3541
export class UtilsError extends ExtendableError {
3642
constructor(message: string) {
3743
super(message);

modules/sdk-core/test/unit/account-lib/baseCoin/messages/baseMessage.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BaseCoin } from '@bitgo/statics';
22
import sinon from 'sinon';
33
import should from 'should';
44
import { MessageStandardType } from '../../../../../src';
5-
import { messageSamples, TestBaseMessage } from './fixtures';
5+
import { messageSamples, TestMessage } from './fixtures';
66

77
describe('Base Message', () => {
88
let coinConfig: sinon.SinonStubbedInstance<BaseCoin>;
@@ -12,7 +12,7 @@ describe('Base Message', () => {
1212
});
1313

1414
it('should initialize with default values', () => {
15-
const message = new TestBaseMessage({
15+
const message = new TestMessage({
1616
coinConfig,
1717
payload: '',
1818
});
@@ -27,7 +27,7 @@ describe('Base Message', () => {
2727
it('should initialize with provided values', () => {
2828
const { payload, type, metadata, signers, signatures } = messageSamples.eip191;
2929

30-
const message = new TestBaseMessage({
30+
const message = new TestMessage({
3131
coinConfig,
3232
payload,
3333
type,
@@ -44,10 +44,10 @@ describe('Base Message', () => {
4444
});
4545

4646
describe('Getters and Setters', () => {
47-
let message: TestBaseMessage;
47+
let message: TestMessage;
4848

4949
beforeEach(() => {
50-
message = new TestBaseMessage({
50+
message = new TestMessage({
5151
coinConfig,
5252
payload: 'test',
5353
});
@@ -111,7 +111,7 @@ describe('Base Message', () => {
111111
describe('getSignablePayload', () => {
112112
it('should return the signablePayload if set', async () => {
113113
const customSignablePayload = '0xabcdef123456';
114-
const message = new TestBaseMessage({
114+
const message = new TestMessage({
115115
coinConfig,
116116
payload: 'original payload',
117117
signablePayload: customSignablePayload,
@@ -123,7 +123,7 @@ describe('Base Message', () => {
123123

124124
it('should return the payload as buffer if signablePayload is not set', async () => {
125125
const payload = 'test payload';
126-
const message = new TestBaseMessage({
126+
const message = new TestMessage({
127127
coinConfig,
128128
payload,
129129
});
@@ -135,7 +135,7 @@ describe('Base Message', () => {
135135

136136
describe('toBroadcastFormat', () => {
137137
it('should throw an error if no signatures are available', async () => {
138-
const message = new TestBaseMessage({
138+
const message = new TestMessage({
139139
coinConfig,
140140
payload: 'test',
141141
signers: ['addr1'],
@@ -147,7 +147,7 @@ describe('Base Message', () => {
147147
});
148148

149149
it('should throw an error if no signers are available', async () => {
150-
const message = new TestBaseMessage({
150+
const message = new TestMessage({
151151
coinConfig,
152152
payload: 'test',
153153
signatures: ['sig1'],
@@ -162,7 +162,7 @@ describe('Base Message', () => {
162162
const { payload, type, metadata, signers, signatures } = messageSamples.eip191;
163163
const customSignablePayload = Buffer.from('custom signable payload');
164164

165-
const message = new TestBaseMessage({
165+
const message = new TestMessage({
166166
coinConfig,
167167
payload,
168168
type,
@@ -193,7 +193,7 @@ describe('Base Message', () => {
193193
},
194194
};
195195

196-
const message = new TestBaseMessage({
196+
const message = new TestMessage({
197197
coinConfig,
198198
payload: 'test',
199199
metadata: nestedMetadata,
@@ -219,7 +219,7 @@ describe('Base Message', () => {
219219
it('should serialize the broadcastable format to JSON string', async () => {
220220
const { payload, type, metadata, signers, signatures } = messageSamples.eip191;
221221

222-
const message = new TestBaseMessage({
222+
const message = new TestMessage({
223223
coinConfig,
224224
payload,
225225
type,

0 commit comments

Comments
 (0)