/
BridgeMessage.ts
318 lines (289 loc) · 9.29 KB
/
BridgeMessage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
import { BigNumber } from '@ethersproject/bignumber';
import { arrayify, hexlify } from '@ethersproject/bytes';
import { TransactionReceipt } from '@ethersproject/abstract-provider';
import { ethers } from 'ethers';
import * as bridge from '@nomad-xyz/contracts-bridge';
import { NomadMessage, AnnotatedDispatch } from '@nomad-xyz/sdk';
import { ResolvedTokenInfo, TokenIdentifier } from './tokens';
import { BridgeContracts } from './BridgeContracts';
import { BridgeContext } from './BridgeContext';
const ACTION_LEN = {
identifier: 1,
tokenId: 36,
transfer: 97,
};
type Transfer = {
type: 'transfer';
to: string;
amount: BigNumber;
detailsHash: string;
allowFast: boolean;
};
export type Action = Transfer;
export type ParsedBridgeMessage<T extends Action> = {
token: TokenIdentifier;
action: T;
};
export type AnyBridgeMessage = TransferMessage;
export type ParsedTransferMessage = ParsedBridgeMessage<Transfer>;
function parseAction(buf: Uint8Array): Action {
// Transfer
if (buf.length === ACTION_LEN.transfer) {
// trim identifer
const actionType = buf[0];
buf = buf.slice(ACTION_LEN.identifier);
return {
type: 'transfer',
to: hexlify(buf.slice(0, 32)),
amount: BigNumber.from(hexlify(buf.slice(32, 64))),
detailsHash: hexlify(buf.slice(64)),
allowFast: actionType === 4,
};
}
throw new Error('Bad action');
}
export function parseBody(messageBody: string): ParsedTransferMessage {
const buf = arrayify(messageBody);
const tokenId = buf.slice(0, 36);
const token = {
domain: Buffer.from(tokenId).readUInt32BE(0),
id: hexlify(tokenId.slice(4, 36)),
};
const action = parseAction(buf.slice(36));
const parsedMessage = {
action,
token,
};
return parsedMessage;
}
/**
* The BridgeMessage extends {@link nomadMessage} with Bridge-specific
* functionality.
*/
export class BridgeMessage extends NomadMessage<BridgeContext> {
readonly token: TokenIdentifier;
readonly fromBridge: BridgeContracts;
readonly toBridge: BridgeContracts;
/**
* @hideconstructor
*/
constructor(
context: BridgeContext,
event: AnnotatedDispatch,
token: TokenIdentifier,
callerKnowsWhatTheyAreDoing: boolean,
) {
if (!callerKnowsWhatTheyAreDoing) {
throw new Error('Use `fromReceipt` to instantiate');
}
super(context, event);
const fromBridge = context.mustGetBridge(this.message.from);
const toBridge = context.mustGetBridge(this.message.destination);
this.fromBridge = fromBridge;
this.toBridge = toBridge;
this.token = token;
}
/**
* Attempt to instantiate a BridgeMessage from an existing
* {@link nomadMessage}
*
* @param context The {@link NomadContext} to use.
* @param nomadMessage The existing nomadMessage
* @returns A Bridge message
* @throws if the message cannot be parsed as a bridge message
*/
static fromNomadMessage(
context: BridgeContext,
nomadMessage: NomadMessage<BridgeContext>,
): AnyBridgeMessage {
const parsedMessageBody = parseBody(nomadMessage.message.body);
return new TransferMessage(
context,
nomadMessage.dispatch,
parsedMessageBody as ParsedTransferMessage,
);
}
/**
* Attempt to instantiate some BridgeMessages from a transaction receipt
*
* @param context The {@link NomadContext} to use.
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt The receipt
* @returns an array of {@link BridgeMessage} objects
* @throws if any message cannot be parsed as a bridge message
*/
static fromReceipt(
context: BridgeContext,
nameOrDomain: string | number,
receipt: TransactionReceipt,
): AnyBridgeMessage[] {
const nomadMessages: NomadMessage<BridgeContext>[] =
NomadMessage.baseFromReceipt(context, nameOrDomain, receipt);
const bridgeMessages: AnyBridgeMessage[] = [];
for (const nomadMessage of nomadMessages) {
try {
const bridgeMessage = BridgeMessage.fromNomadMessage(
context,
nomadMessage,
);
bridgeMessages.push(bridgeMessage);
} catch (e) {
// catch error if nomadMessage isn't a BridgeMessage
}
}
return bridgeMessages;
}
/**
* Attempt to instantiate EXACTLY one BridgeMessage from a transaction receipt
*
* @param context The {@link BridgeContext} to use.
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt The receipt
* @returns an array of {@link BridgeMessage} objects
* @throws if any message cannot be parsed as a bridge message, or if there
* is not EXACTLY 1 BridgeMessage in the receipt
*/
static singleFromReceipt(
context: BridgeContext,
nameOrDomain: string | number,
receipt: TransactionReceipt,
): AnyBridgeMessage {
const messages: AnyBridgeMessage[] = BridgeMessage.fromReceipt(
context,
nameOrDomain,
receipt,
);
if (messages.length !== 1) {
throw new Error('Expected single Dispatch in transaction');
}
return messages[0];
}
/**
* Attempt to instantiate some BridgeMessages from a transaction hash by
* retrieving and parsing the receipt.
*
* @param context The {@link NomadContext} to use.
* @param nameOrDomain the domain on which the receipt was logged
* @param transactionHash The transaction hash
* @returns an array of {@link BridgeMessage} objects
* @throws if any message cannot be parsed as a bridge message
*/
static async fromTransactionHash(
context: BridgeContext,
nameOrDomain: string | number,
transactionHash: string,
): Promise<AnyBridgeMessage[]> {
const provider = context.mustGetProvider(nameOrDomain);
const receipt = await provider.getTransactionReceipt(transactionHash);
if (!receipt) {
throw new Error(`No receipt for ${transactionHash} on ${nameOrDomain}`);
}
return BridgeMessage.fromReceipt(context, nameOrDomain, receipt);
}
/**
* Attempt to instantiate EXACTLY one BridgeMessages from a transaction hash
* by retrieving and parsing the receipt.
*
* @param context The {@link NomadContext} to use.
* @param nameOrDomain the domain on which the receipt was logged
* @param transactionHash The transaction hash
* @returns an array of {@link BridgeMessage} objects
* @throws if any message cannot be parsed as a bridge message, or if there is
* not EXACTLY one such message
*/
static async singleFromTransactionHash(
context: BridgeContext,
nameOrDomain: string | number,
transactionHash: string,
): Promise<AnyBridgeMessage> {
const provider = context.mustGetProvider(nameOrDomain);
const receipt = await provider.getTransactionReceipt(transactionHash);
if (!receipt) {
throw new Error(`No receipt for ${transactionHash} on ${nameOrDomain}`);
}
return BridgeMessage.singleFromReceipt(context, nameOrDomain, receipt);
}
/**
* Resolves the asset that is being transfered
*
* WARNING: do not hold references to these contract, as they will not be
* reconnected in the event the chain connection changes.
*
* @returns The resolved token information.
*/
async asset(): Promise<ResolvedTokenInfo> {
return await this.context.resolveRepresentations(this.token);
}
/**
* Resolves an interface for the asset that is being transfered on the chain
* FROM WHICH it is being transferred
*
* WARNING: do not hold references to this contract, as it will not be
* reconnected in the event the chain connection changes.
*
* @returns The resolved token interface.
*/
async assetAtOrigin(): Promise<bridge.ERC20 | undefined> {
return (await this.asset()).tokens.get(this.origin);
}
/**
* Resolves an interface for the asset that is being transfered on the chain
* TO WHICH it is being transferred
*
* WARNING: do not hold references to this contract, as it will not be
* reconnected in the event the chain connection changes.
*
* @returns The resolved token interface.
*/
async assetAtDestination(): Promise<bridge.ERC20 | undefined> {
return (await this.asset()).tokens.get(this.destination);
}
}
/**
* A TransferMessage extends the {@link BridgeMessage} with transfer-specific
* functionality.
*/
export class TransferMessage extends BridgeMessage {
action: Transfer;
constructor(
context: BridgeContext,
event: AnnotatedDispatch,
parsed: ParsedTransferMessage,
) {
super(context, event, parsed.token, true);
this.action = parsed.action;
}
/**
* Check if the transfer has been prefilled using the fast liquidity system.
*
* @returns true if the transfer has been prefilled. Else false.
*/
async currentlyPrefilled(): Promise<boolean> {
const bridge = this.context.mustGetBridge(this.destination);
const lpAddress = await bridge.bridgeRouter.liquidityProvider(
this.prefillId,
);
if (lpAddress !== ethers.constants.AddressZero) {
return true;
}
return false;
}
/**
* The amount of tokens being transferred (in the smallest unit)
*/
get amount(): BigNumber {
return this.action.amount;
}
/**
* The identifier for the recipient of the tokens
*/
get to(): string {
return this.action.to;
}
/**
* The ID used for prefilling this transfer message.
*/
get prefillId(): string {
return this.bodyHash;
}
}