Skip to content

Commit ce78b54

Browse files
committed
refactor(account-lib): common build() function in BaseMessage class
TICKET: COIN-4737
1 parent d5af5c8 commit ce78b54

File tree

10 files changed

+95
-165
lines changed

10 files changed

+95
-165
lines changed
Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EIP191Message } from './eip191Message';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3-
import { BaseMessageBuilder, IMessage, MessageStandardType } from '@bitgo/sdk-core';
3+
import { BaseMessageBuilder, IMessage, MessageOptions, MessageStandardType } from '@bitgo/sdk-core';
44

55
/**
66
* Builder for EIP-191 messages
@@ -15,30 +15,11 @@ export class Eip191MessageBuilder extends BaseMessageBuilder {
1515
}
1616

1717
/**
18-
* Build a signable message using the EIP-191 standard
19-
* with previously set input and metadata
20-
* @returns A signable message
18+
* Builds an EIP-191 message instance with the provided options
19+
* @param options Options to create the message
20+
* @returns A Promise that resolves to an EIP191Message instance
2121
*/
22-
public async build(): Promise<IMessage> {
23-
try {
24-
if (!this.payload) {
25-
throw new Error('Message payload must be set before building the message');
26-
}
27-
return new EIP191Message({
28-
coinConfig: this.coinConfig,
29-
payload: this.payload,
30-
signatures: this.signatures,
31-
signers: this.signers,
32-
metadata: {
33-
...this.metadata,
34-
encoding: 'utf8',
35-
},
36-
});
37-
} catch (err) {
38-
if (err instanceof Error) {
39-
throw err;
40-
}
41-
throw new Error('Failed to build EIP-191 message');
42-
}
22+
async buildMessage(options: MessageOptions): Promise<IMessage> {
23+
return new EIP191Message(options);
4324
}
4425
}

modules/account-lib/test/unit/fixtures.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,8 @@ export class MockMessageBuilder extends BaseMessageBuilder {
2424
super(coinConfig, type);
2525
}
2626

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-
});
27+
async buildMessage(options: MessageOptions): Promise<IMessage> {
28+
return new MockMessage(options);
3829
}
3930

4031
async fromBroadcastFormat(broadcastMessage: any): Promise<IMessage> {

modules/sdk-coin-ada/src/lib/messages/cip8/cip8Message.ts

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,54 +13,28 @@ export class Cip8Message extends BaseMessage {
1313
});
1414
}
1515

16-
/**
17-
* Validates required fields and returns common setup objects
18-
* @private
19-
*/
20-
private validateAndGetCommonSetup() {
21-
if (!this.payload) {
22-
throw new Error('Payload is required to build a CIP8 message');
23-
}
24-
if (!this.signers || this.signers.length === 0) {
25-
throw new Error('A signer address is required to build a CIP8 message');
26-
}
27-
28-
let cslAddress: CardanoSL.Address;
29-
try {
30-
cslAddress = CardanoSL.Address.from_bech32(this.signers[0]);
31-
} catch (error) {
32-
// Convert string errors to proper Error objects
33-
if (typeof error === 'string') {
34-
throw new Error(`Invalid signer address: ${error}`);
35-
}
36-
throw error;
37-
}
38-
39-
const addressCborBytes = cslAddress.to_bytes();
40-
41-
return { addressCborBytes };
42-
}
43-
4416
/**
4517
* Returns the hash of the CIP-8 prefixed message
4618
*/
4719
async getSignablePayload(): Promise<string | Buffer> {
4820
if (!this.signablePayload) {
49-
this.signablePayload = this.buildSignablePayload();
21+
const { addressCborBytes } = this.validateAndGetCommonSetup();
22+
const { sigStructureCborBytes } = createCSLSigStructure(addressCborBytes, this.payload);
23+
this.signablePayload = Buffer.from(sigStructureCborBytes);
5024
}
5125
return this.signablePayload;
5226
}
5327

54-
/**
55-
* Builds the signable payload for a CIP8 message
56-
* @returns The signable payload as a Buffer
28+
/*
29+
* Returns broadcastable signatures in COSE format according to CIP8 standard
30+
*
31+
* This method transforms the internal signatures into a format suitable for broadcasting
32+
* by constructing COSE (CBOR Object Signing and Encryption) objects that comply with
33+
* the CIP8 message signing specification.
34+
*
35+
* @returns Array of signatures with COSE-formatted signature data and public keys
36+
* @throws Error if required setup validation fails
5737
*/
58-
buildSignablePayload(): string | Buffer {
59-
const { addressCborBytes } = this.validateAndGetCommonSetup();
60-
const { sigStructureCborBytes } = createCSLSigStructure(addressCborBytes, this.payload);
61-
return Buffer.from(sigStructureCborBytes);
62-
}
63-
6438
getBroadcastableSignatures(): Signature[] {
6539
if (!this.signatures.length) {
6640
return [];
@@ -88,4 +62,31 @@ export class Cip8Message extends BaseMessage {
8862
},
8963
];
9064
}
65+
66+
/**
67+
* Validates required fields and returns common setup objects
68+
* @private
69+
*/
70+
private validateAndGetCommonSetup() {
71+
if (!this.payload) {
72+
throw new Error('Payload is required to build a CIP8 message');
73+
}
74+
if (!this.signers || this.signers.length === 0) {
75+
throw new Error('A signer address is required to build a CIP8 message');
76+
}
77+
78+
let cslAddress: CardanoSL.Address;
79+
try {
80+
cslAddress = CardanoSL.Address.from_bech32(this.signers[0]);
81+
} catch (error) {
82+
// Convert string errors to proper Error objects
83+
if (typeof error === 'string') {
84+
throw new Error(`Invalid signer address: ${error}`);
85+
}
86+
throw error;
87+
}
88+
89+
const addressCborBytes = cslAddress.to_bytes();
90+
return { addressCborBytes };
91+
}
9192
}
Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Cip8Message } from './cip8Message';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3-
import { BaseMessageBuilder, IMessage, MessageStandardType } from '@bitgo/sdk-core';
3+
import { BaseMessageBuilder, IMessage, MessageOptions, MessageStandardType } from '@bitgo/sdk-core';
44

55
/**
66
* Builder for CIP-8 messages
@@ -15,30 +15,11 @@ export class Cip8MessageBuilder extends BaseMessageBuilder {
1515
}
1616

1717
/**
18-
* Build a signable message using the CIP-8 standard
19-
* with previously set input and metadata
20-
* @returns A signable message
18+
* Builds a CIP-8 message instance with the provided options
19+
* @param options Options to create the message
20+
* @returns A Promise that resolves to a Cip8Message instance
2121
*/
22-
public async build(): Promise<IMessage> {
23-
try {
24-
if (!this.payload) {
25-
throw new Error('Message payload must be set before building the message');
26-
}
27-
return new Cip8Message({
28-
coinConfig: this.coinConfig,
29-
payload: this.payload,
30-
signatures: this.signatures,
31-
signers: this.signers,
32-
metadata: {
33-
...this.metadata,
34-
encoding: 'utf8',
35-
},
36-
});
37-
} catch (err) {
38-
if (err instanceof Error) {
39-
throw err;
40-
}
41-
throw new Error('Failed to build CIP-8 message');
42-
}
22+
async buildMessage(options: MessageOptions): Promise<IMessage> {
23+
return new Cip8Message(options);
4324
}
4425
}

modules/sdk-coin-ada/test/unit/messages/cip8/cip8Message.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ describe('Cip8Message', function () {
105105

106106
it('should cache signable payload', async function () {
107107
const message = new Cip8Message(createDefaultMessageOptions());
108-
const buildSignablePayloadSpy = sandbox.spy(message as any, 'buildSignablePayload');
109108

110109
const payload1 = await message.getSignablePayload();
111110
const payload2 = await message.getSignablePayload();
@@ -115,27 +114,6 @@ describe('Cip8Message', function () {
115114

116115
// Payloads should be the same
117116
should.equal(payload1, payload2);
118-
119-
// buildSignablePayload should be called only once
120-
buildSignablePayloadSpy.calledOnce.should.be.true();
121-
});
122-
});
123-
124-
describe('buildSignablePayload', function () {
125-
it('should create a buffer from signature structure', function () {
126-
const message = new Cip8Message(createDefaultMessageOptions());
127-
const payload = (message as any).buildSignablePayload();
128-
129-
should.exist(payload);
130-
should.ok(Buffer.isBuffer(payload));
131-
});
132-
133-
it('should throw when validation fails', async function () {
134-
const options = createDefaultMessageOptions();
135-
options.payload = undefined as any;
136-
const message = new Cip8Message(options);
137-
138-
await should(message.getSignablePayload()).be.rejectedWith(`Payload is required to build a CIP8 message`);
139117
});
140118
});
141119

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export abstract class BaseMessage implements IMessage {
2525
*/
2626
protected constructor(options: MessageOptions) {
2727
this.coinConfig = options.coinConfig;
28+
this.payload = options.payload;
2829
this.type = options.type || MessageStandardType.UNKNOWN;
29-
this.payload = options.payload || '';
3030
this.signablePayload = options.signablePayload;
3131
this.metadata = options.metadata || {};
3232

@@ -117,6 +117,9 @@ export abstract class BaseMessage implements IMessage {
117117
*/
118118
abstract getSignablePayload(): Promise<string | Buffer>;
119119

120+
/**
121+
* Gets the signatures in a format suitable for broadcasting
122+
*/
120123
getBroadcastableSignatures(): Signature[] {
121124
return this.signatures;
122125
}

modules/sdk-core/src/account-lib/baseCoin/messages/baseMessageBuilder.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BroadcastableMessage, MessagePayload, MessageStandardType } from '../../../bitgo';
1+
import { BroadcastableMessage, MessageOptions, MessagePayload, MessageStandardType } from '../../../bitgo';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { IMessage, IMessageBuilder } from './iface';
44
import { deserializeSignatures, Signature } from '../iface';
@@ -120,10 +120,33 @@ export abstract class BaseMessageBuilder implements IMessageBuilder {
120120

121121
/**
122122
* Builds a message using the previously set payload and metadata
123-
* This abstract method must be implemented by each specific builder
124123
* @returns A Promise resolving to the built IMessage
125124
*/
126-
abstract build(): Promise<IMessage>;
125+
public async build(): Promise<IMessage> {
126+
try {
127+
if (!this.payload) {
128+
throw new Error('Message payload must be set before building the message');
129+
}
130+
return this.buildMessage({
131+
coinConfig: this.coinConfig,
132+
payload: this.payload,
133+
type: this.type,
134+
signatures: this.signatures,
135+
signers: this.signers,
136+
metadata: {
137+
...this.metadata,
138+
encoding: 'utf8',
139+
},
140+
});
141+
} catch (err) {
142+
if (err instanceof Error) {
143+
throw err;
144+
}
145+
throw new Error(`Failed to build message of type ${this.type}`);
146+
}
147+
}
148+
149+
protected abstract buildMessage(options: MessageOptions): Promise<IMessage>;
127150

128151
/**
129152
* Parse a broadcastable message back into a message
Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SimpleMessage } from './simpleMessage';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { BaseMessageBuilder } from '../baseMessageBuilder';
4-
import { MessageStandardType } from '../../../../bitgo';
4+
import { MessageOptions, MessageStandardType } from '../../../../bitgo';
55
import { IMessage } from '../iface';
66

77
/**
@@ -17,30 +17,11 @@ export class SimpleMessageBuilder extends BaseMessageBuilder {
1717
}
1818

1919
/**
20-
* Build a signable message using the EIP-191 standard
21-
* with previously set input and metadata
22-
* @returns A signable message
20+
* Builds a SimpleMessage instance with the provided options
21+
* @param options Options to create the message
22+
* @returns A Promise that resolves to a SimpleMessage instance
2323
*/
24-
public async build(): Promise<IMessage> {
25-
try {
26-
if (!this.payload) {
27-
throw new Error('Message payload must be set before building the message');
28-
}
29-
return new SimpleMessage({
30-
coinConfig: this.coinConfig,
31-
payload: this.payload,
32-
signatures: this.signatures,
33-
signers: this.signers,
34-
metadata: {
35-
...this.metadata,
36-
encoding: 'utf8',
37-
},
38-
});
39-
} catch (err) {
40-
if (err instanceof Error) {
41-
throw err;
42-
}
43-
throw new Error('Failed to build EIP-191 message');
44-
}
24+
public async buildMessage(options: MessageOptions): Promise<IMessage> {
25+
return new SimpleMessage(options);
4526
}
4627
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('Base Message Builder', () => {
7373

7474
it('should build a message with the correct properties', async () => {
7575
const payload = 'test message';
76-
const metadata = { foo: 'bar' };
76+
const metadata = { foo: 'bar', encoding: 'utf8' };
7777
const signatures = [
7878
{
7979
publicKey: { pub: 'pubKey1' },
@@ -107,7 +107,7 @@ describe('Base Message Builder', () => {
107107

108108
it('should correctly handle toBroadcastFormat', async () => {
109109
const payload = 'hello world';
110-
const metadata = { version: '1.0' };
110+
const metadata = { version: '1.0', encoding: 'utf8' };
111111
const signatures = [
112112
{
113113
publicKey: { pub: 'pubKey1' },
@@ -152,7 +152,7 @@ describe('Base Message Builder', () => {
152152
},
153153
],
154154
signers: ['addr1', 'addr2'],
155-
metadata: { chainId: 1 },
155+
metadata: { chainId: 1, encoding: 'utf8' },
156156
};
157157

158158
const message = await builder.fromBroadcastFormat(broadcastMessage);
@@ -184,7 +184,7 @@ describe('Base Message Builder', () => {
184184
payload: payload,
185185
serializedSignatures: serializeSignatures(signatures),
186186
signers: signers,
187-
metadata: {},
187+
metadata: { encoding: 'utf8' },
188188
});
189189

190190
should.equal(broadcastString, expectedJson);

0 commit comments

Comments
 (0)