damm-sdk 1.4.30 → 1.4.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +75697 -54442
- package/dist/index.cjs.map +191 -56
- package/dist/index.js +49311 -31493
- package/dist/index.js.map +169 -60
- package/dist/integrations/cctp/cctp.iris.api.d.ts +300 -0
- package/dist/integrations/cctp/cctp.iris.api.d.ts.map +1 -0
- package/dist/integrations/cctp/cctp.message.decoder.d.ts +80 -0
- package/dist/integrations/cctp/cctp.message.decoder.d.ts.map +1 -0
- package/dist/integrations/cctp/index.d.ts +2 -0
- package/dist/integrations/cctp/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/integrations/cctp/cctp.iris.api.ts +164 -0
- package/src/integrations/cctp/cctp.message.decoder.ts +189 -0
- package/src/integrations/cctp/index.ts +2 -0
- package/src/lib/contractsRegistry.json +2 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for Circle's iris-api V2 — the attestation service that watches
|
|
3
|
+
* CCTP V2 `depositForBurn` events and publishes signed attestations that
|
|
4
|
+
* the destination chain needs to mint USDC via `receiveMessage`.
|
|
5
|
+
*
|
|
6
|
+
* Only the production endpoint (https://iris-api.circle.com) is supported.
|
|
7
|
+
* Sandbox is intentionally omitted — we do not bridge via sandbox, and
|
|
8
|
+
* keeping a single base URL removes a class of foot-guns.
|
|
9
|
+
*
|
|
10
|
+
* API reference: https://developers.circle.com/stablecoins/iris-api-reference
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import type { HexString } from "../../types";
|
|
14
|
+
export declare const CCTP_IRIS_API_BASE: "https://iris-api.circle.com";
|
|
15
|
+
export declare const IrisDecodedBurnBodySchema: z.ZodPipe<z.ZodObject<{
|
|
16
|
+
burnToken: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
17
|
+
mintRecipient: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
18
|
+
amount: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
19
|
+
messageSender: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
20
|
+
maxFee: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
21
|
+
feeExecuted: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
22
|
+
expirationBlock: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
23
|
+
hookData: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>>;
|
|
24
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
25
|
+
burnToken: `0x${string}`;
|
|
26
|
+
mintRecipient: `0x${string}`;
|
|
27
|
+
amount: bigint;
|
|
28
|
+
messageSender: `0x${string}`;
|
|
29
|
+
maxFee: bigint;
|
|
30
|
+
feeExecuted: bigint;
|
|
31
|
+
expirationBlock: bigint;
|
|
32
|
+
hookData: `0x${string}` | null;
|
|
33
|
+
}>, {
|
|
34
|
+
burnToken: `0x${string}`;
|
|
35
|
+
mintRecipient: `0x${string}`;
|
|
36
|
+
amount: bigint;
|
|
37
|
+
messageSender: `0x${string}`;
|
|
38
|
+
maxFee: bigint;
|
|
39
|
+
feeExecuted: bigint;
|
|
40
|
+
expirationBlock: bigint;
|
|
41
|
+
hookData: `0x${string}` | null;
|
|
42
|
+
}>>;
|
|
43
|
+
export type IrisDecodedBurnBody = z.infer<typeof IrisDecodedBurnBodySchema>;
|
|
44
|
+
export declare const IrisDecodedMessageSchema: z.ZodPipe<z.ZodObject<{
|
|
45
|
+
sourceDomain: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
46
|
+
destinationDomain: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
47
|
+
nonce: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
48
|
+
sender: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
49
|
+
recipient: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
50
|
+
destinationCaller: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
51
|
+
minFinalityThreshold: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
52
|
+
finalityThresholdExecuted: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
53
|
+
messageBody: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
54
|
+
decodedMessageBody: z.ZodNullable<z.ZodPipe<z.ZodObject<{
|
|
55
|
+
burnToken: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
56
|
+
mintRecipient: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
57
|
+
amount: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
58
|
+
messageSender: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
59
|
+
maxFee: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
60
|
+
feeExecuted: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
61
|
+
expirationBlock: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
62
|
+
hookData: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>>;
|
|
63
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
64
|
+
burnToken: `0x${string}`;
|
|
65
|
+
mintRecipient: `0x${string}`;
|
|
66
|
+
amount: bigint;
|
|
67
|
+
messageSender: `0x${string}`;
|
|
68
|
+
maxFee: bigint;
|
|
69
|
+
feeExecuted: bigint;
|
|
70
|
+
expirationBlock: bigint;
|
|
71
|
+
hookData: `0x${string}` | null;
|
|
72
|
+
}>, {
|
|
73
|
+
burnToken: `0x${string}`;
|
|
74
|
+
mintRecipient: `0x${string}`;
|
|
75
|
+
amount: bigint;
|
|
76
|
+
messageSender: `0x${string}`;
|
|
77
|
+
maxFee: bigint;
|
|
78
|
+
feeExecuted: bigint;
|
|
79
|
+
expirationBlock: bigint;
|
|
80
|
+
hookData: `0x${string}` | null;
|
|
81
|
+
}>>>;
|
|
82
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
83
|
+
sourceDomain: number;
|
|
84
|
+
destinationDomain: number;
|
|
85
|
+
nonce: `0x${string}`;
|
|
86
|
+
sender: `0x${string}`;
|
|
87
|
+
recipient: `0x${string}`;
|
|
88
|
+
destinationCaller: `0x${string}`;
|
|
89
|
+
minFinalityThreshold: number;
|
|
90
|
+
finalityThresholdExecuted: number;
|
|
91
|
+
messageBody: `0x${string}`;
|
|
92
|
+
decodedMessageBody: Readonly<{
|
|
93
|
+
burnToken: `0x${string}`;
|
|
94
|
+
mintRecipient: `0x${string}`;
|
|
95
|
+
amount: bigint;
|
|
96
|
+
messageSender: `0x${string}`;
|
|
97
|
+
maxFee: bigint;
|
|
98
|
+
feeExecuted: bigint;
|
|
99
|
+
expirationBlock: bigint;
|
|
100
|
+
hookData: `0x${string}` | null;
|
|
101
|
+
}> | null;
|
|
102
|
+
}>, {
|
|
103
|
+
sourceDomain: number;
|
|
104
|
+
destinationDomain: number;
|
|
105
|
+
nonce: `0x${string}`;
|
|
106
|
+
sender: `0x${string}`;
|
|
107
|
+
recipient: `0x${string}`;
|
|
108
|
+
destinationCaller: `0x${string}`;
|
|
109
|
+
minFinalityThreshold: number;
|
|
110
|
+
finalityThresholdExecuted: number;
|
|
111
|
+
messageBody: `0x${string}`;
|
|
112
|
+
decodedMessageBody: Readonly<{
|
|
113
|
+
burnToken: `0x${string}`;
|
|
114
|
+
mintRecipient: `0x${string}`;
|
|
115
|
+
amount: bigint;
|
|
116
|
+
messageSender: `0x${string}`;
|
|
117
|
+
maxFee: bigint;
|
|
118
|
+
feeExecuted: bigint;
|
|
119
|
+
expirationBlock: bigint;
|
|
120
|
+
hookData: `0x${string}` | null;
|
|
121
|
+
}> | null;
|
|
122
|
+
}>>;
|
|
123
|
+
export type IrisDecodedMessage = z.infer<typeof IrisDecodedMessageSchema>;
|
|
124
|
+
/**
|
|
125
|
+
* Possible values of the `status` field. Circle does not publish the full
|
|
126
|
+
* enum, so we accept any string and surface the observed common values as
|
|
127
|
+
* a type hint. Known values at time of writing:
|
|
128
|
+
* - `complete` — attestation signed and available
|
|
129
|
+
* - `pending_confirmations` — still waiting for source-chain finality
|
|
130
|
+
*/
|
|
131
|
+
export type IrisMessageStatus = "complete" | "pending_confirmations" | (string & {});
|
|
132
|
+
export declare const IrisMessageSchema: z.ZodPipe<z.ZodObject<{
|
|
133
|
+
attestation: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>>;
|
|
134
|
+
message: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>>;
|
|
135
|
+
eventNonce: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
136
|
+
cctpVersion: z.ZodNumber;
|
|
137
|
+
status: z.ZodType<IrisMessageStatus, unknown, z.core.$ZodTypeInternals<IrisMessageStatus, unknown>>;
|
|
138
|
+
decodedMessage: z.ZodNullable<z.ZodPipe<z.ZodObject<{
|
|
139
|
+
sourceDomain: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
140
|
+
destinationDomain: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
141
|
+
nonce: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
142
|
+
sender: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
143
|
+
recipient: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
144
|
+
destinationCaller: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
145
|
+
minFinalityThreshold: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
146
|
+
finalityThresholdExecuted: z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>;
|
|
147
|
+
messageBody: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
148
|
+
decodedMessageBody: z.ZodNullable<z.ZodPipe<z.ZodObject<{
|
|
149
|
+
burnToken: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
150
|
+
mintRecipient: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
151
|
+
amount: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
152
|
+
messageSender: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
153
|
+
maxFee: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
154
|
+
feeExecuted: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
155
|
+
expirationBlock: z.ZodPipe<z.ZodString, z.ZodTransform<bigint, string>>;
|
|
156
|
+
hookData: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>>;
|
|
157
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
158
|
+
burnToken: `0x${string}`;
|
|
159
|
+
mintRecipient: `0x${string}`;
|
|
160
|
+
amount: bigint;
|
|
161
|
+
messageSender: `0x${string}`;
|
|
162
|
+
maxFee: bigint;
|
|
163
|
+
feeExecuted: bigint;
|
|
164
|
+
expirationBlock: bigint;
|
|
165
|
+
hookData: `0x${string}` | null;
|
|
166
|
+
}>, {
|
|
167
|
+
burnToken: `0x${string}`;
|
|
168
|
+
mintRecipient: `0x${string}`;
|
|
169
|
+
amount: bigint;
|
|
170
|
+
messageSender: `0x${string}`;
|
|
171
|
+
maxFee: bigint;
|
|
172
|
+
feeExecuted: bigint;
|
|
173
|
+
expirationBlock: bigint;
|
|
174
|
+
hookData: `0x${string}` | null;
|
|
175
|
+
}>>>;
|
|
176
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
177
|
+
sourceDomain: number;
|
|
178
|
+
destinationDomain: number;
|
|
179
|
+
nonce: `0x${string}`;
|
|
180
|
+
sender: `0x${string}`;
|
|
181
|
+
recipient: `0x${string}`;
|
|
182
|
+
destinationCaller: `0x${string}`;
|
|
183
|
+
minFinalityThreshold: number;
|
|
184
|
+
finalityThresholdExecuted: number;
|
|
185
|
+
messageBody: `0x${string}`;
|
|
186
|
+
decodedMessageBody: Readonly<{
|
|
187
|
+
burnToken: `0x${string}`;
|
|
188
|
+
mintRecipient: `0x${string}`;
|
|
189
|
+
amount: bigint;
|
|
190
|
+
messageSender: `0x${string}`;
|
|
191
|
+
maxFee: bigint;
|
|
192
|
+
feeExecuted: bigint;
|
|
193
|
+
expirationBlock: bigint;
|
|
194
|
+
hookData: `0x${string}` | null;
|
|
195
|
+
}> | null;
|
|
196
|
+
}>, {
|
|
197
|
+
sourceDomain: number;
|
|
198
|
+
destinationDomain: number;
|
|
199
|
+
nonce: `0x${string}`;
|
|
200
|
+
sender: `0x${string}`;
|
|
201
|
+
recipient: `0x${string}`;
|
|
202
|
+
destinationCaller: `0x${string}`;
|
|
203
|
+
minFinalityThreshold: number;
|
|
204
|
+
finalityThresholdExecuted: number;
|
|
205
|
+
messageBody: `0x${string}`;
|
|
206
|
+
decodedMessageBody: Readonly<{
|
|
207
|
+
burnToken: `0x${string}`;
|
|
208
|
+
mintRecipient: `0x${string}`;
|
|
209
|
+
amount: bigint;
|
|
210
|
+
messageSender: `0x${string}`;
|
|
211
|
+
maxFee: bigint;
|
|
212
|
+
feeExecuted: bigint;
|
|
213
|
+
expirationBlock: bigint;
|
|
214
|
+
hookData: `0x${string}` | null;
|
|
215
|
+
}> | null;
|
|
216
|
+
}>>>;
|
|
217
|
+
delayReason: z.ZodNullable<z.ZodString>;
|
|
218
|
+
}, z.core.$strip>, z.ZodTransform<Readonly<{
|
|
219
|
+
attestation: `0x${string}` | null;
|
|
220
|
+
message: `0x${string}` | null;
|
|
221
|
+
eventNonce: `0x${string}`;
|
|
222
|
+
cctpVersion: number;
|
|
223
|
+
status: IrisMessageStatus;
|
|
224
|
+
decodedMessage: Readonly<{
|
|
225
|
+
sourceDomain: number;
|
|
226
|
+
destinationDomain: number;
|
|
227
|
+
nonce: `0x${string}`;
|
|
228
|
+
sender: `0x${string}`;
|
|
229
|
+
recipient: `0x${string}`;
|
|
230
|
+
destinationCaller: `0x${string}`;
|
|
231
|
+
minFinalityThreshold: number;
|
|
232
|
+
finalityThresholdExecuted: number;
|
|
233
|
+
messageBody: `0x${string}`;
|
|
234
|
+
decodedMessageBody: Readonly<{
|
|
235
|
+
burnToken: `0x${string}`;
|
|
236
|
+
mintRecipient: `0x${string}`;
|
|
237
|
+
amount: bigint;
|
|
238
|
+
messageSender: `0x${string}`;
|
|
239
|
+
maxFee: bigint;
|
|
240
|
+
feeExecuted: bigint;
|
|
241
|
+
expirationBlock: bigint;
|
|
242
|
+
hookData: `0x${string}` | null;
|
|
243
|
+
}> | null;
|
|
244
|
+
}> | null;
|
|
245
|
+
delayReason: string | null;
|
|
246
|
+
}>, {
|
|
247
|
+
attestation: `0x${string}` | null;
|
|
248
|
+
message: `0x${string}` | null;
|
|
249
|
+
eventNonce: `0x${string}`;
|
|
250
|
+
cctpVersion: number;
|
|
251
|
+
status: IrisMessageStatus;
|
|
252
|
+
decodedMessage: Readonly<{
|
|
253
|
+
sourceDomain: number;
|
|
254
|
+
destinationDomain: number;
|
|
255
|
+
nonce: `0x${string}`;
|
|
256
|
+
sender: `0x${string}`;
|
|
257
|
+
recipient: `0x${string}`;
|
|
258
|
+
destinationCaller: `0x${string}`;
|
|
259
|
+
minFinalityThreshold: number;
|
|
260
|
+
finalityThresholdExecuted: number;
|
|
261
|
+
messageBody: `0x${string}`;
|
|
262
|
+
decodedMessageBody: Readonly<{
|
|
263
|
+
burnToken: `0x${string}`;
|
|
264
|
+
mintRecipient: `0x${string}`;
|
|
265
|
+
amount: bigint;
|
|
266
|
+
messageSender: `0x${string}`;
|
|
267
|
+
maxFee: bigint;
|
|
268
|
+
feeExecuted: bigint;
|
|
269
|
+
expirationBlock: bigint;
|
|
270
|
+
hookData: `0x${string}` | null;
|
|
271
|
+
}> | null;
|
|
272
|
+
}> | null;
|
|
273
|
+
delayReason: string | null;
|
|
274
|
+
}>>;
|
|
275
|
+
export type IrisMessage = z.infer<typeof IrisMessageSchema>;
|
|
276
|
+
export type FetchCctpMessagesArgs = Readonly<{
|
|
277
|
+
sourceChainId: number;
|
|
278
|
+
txHash: HexString;
|
|
279
|
+
}>;
|
|
280
|
+
/**
|
|
281
|
+
* Fetch all CCTP V2 messages produced by a source-chain transaction.
|
|
282
|
+
*
|
|
283
|
+
* A single `depositForBurn` emits one message, but the API returns an
|
|
284
|
+
* array to accommodate batched / compound transactions. Returns `[]` when
|
|
285
|
+
* iris has not yet indexed the tx.
|
|
286
|
+
*
|
|
287
|
+
* @throws if `sourceChainId` is not in the SDK's CCTP V2 domain registry
|
|
288
|
+
* @throws if iris responds with a non-2xx status
|
|
289
|
+
* @throws if the response body fails schema validation (e.g., new iris shape)
|
|
290
|
+
*/
|
|
291
|
+
export declare const fetchCctpMessages: ({ sourceChainId, txHash, }: FetchCctpMessagesArgs) => Promise<readonly IrisMessage[]>;
|
|
292
|
+
/**
|
|
293
|
+
* Convenience: fetch the first message for a source-chain tx, or `null`
|
|
294
|
+
* if iris has no messages indexed for that tx.
|
|
295
|
+
*
|
|
296
|
+
* Use {@link fetchCctpMessages} directly when a single tx may have burned
|
|
297
|
+
* for multiple destinations (rare — CCTP V2 `depositForBurn` is 1:1).
|
|
298
|
+
*/
|
|
299
|
+
export declare const fetchCctpMessage: (args: FetchCctpMessagesArgs) => Promise<IrisMessage | null>;
|
|
300
|
+
//# sourceMappingURL=cctp.iris.api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cctp.iris.api.d.ts","sourceRoot":"","sources":["../../../src/integrations/cctp/cctp.iris.api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,eAAO,MAAM,kBAAkB,+BAAyC,CAAC;AAkCzE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;GAWC,CAAC;AAExC,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAaE,CAAC;AAExC,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAI1E;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,uBAAuB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAErF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAUS,CAAC;AAExC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAQ5D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,SAAS,CAAC;CACrB,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,+BAG3B,qBAAqB,KAAG,QAAQ,SAAS,WAAW,EAAE,CAexD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,SAAgB,qBAAqB,KAAG,QAAQ,WAAW,GAAG,IAAI,CAG9F,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure byte-level decoder for CCTP V2 cross-chain messages.
|
|
3
|
+
*
|
|
4
|
+
* Use when you need to verify or inspect the contents of a CCTP V2 message
|
|
5
|
+
* without trusting the pre-decoded fields from iris-api (defense in depth),
|
|
6
|
+
* or when working with a raw message blob produced by a source-chain tx.
|
|
7
|
+
*
|
|
8
|
+
* Message format reference:
|
|
9
|
+
* https://developers.circle.com/stablecoins/message-format
|
|
10
|
+
*
|
|
11
|
+
* The outer header (148 bytes) wraps a variable-length body. For USDC burn
|
|
12
|
+
* transfers the body is a `BurnMessage` V2 — the only body shape this
|
|
13
|
+
* module supports (other body shapes are non-USDC payloads that cli-damm
|
|
14
|
+
* does not currently finalize).
|
|
15
|
+
*/
|
|
16
|
+
import type { Address } from "viem";
|
|
17
|
+
import type { HexString } from "../../types";
|
|
18
|
+
/** Version byte at the start of a CCTP V2 header. */
|
|
19
|
+
export declare const CCTP_V2_HEADER_VERSION: 1;
|
|
20
|
+
/** Version byte at the start of a CCTP V2 BurnMessage body. */
|
|
21
|
+
export declare const CCTP_V2_BURN_BODY_VERSION: 1;
|
|
22
|
+
/**
|
|
23
|
+
* Decoded CCTP V2 header. All bytes32 fields are preserved as 0x-prefixed
|
|
24
|
+
* hex strings (not narrowed to addresses) except `sender` and `recipient`,
|
|
25
|
+
* which Circle guarantees are address-padded bytes32 values.
|
|
26
|
+
*/
|
|
27
|
+
export type CctpV2Header = Readonly<{
|
|
28
|
+
version: number;
|
|
29
|
+
sourceDomain: number;
|
|
30
|
+
destinationDomain: number;
|
|
31
|
+
nonce: HexString;
|
|
32
|
+
sender: Address;
|
|
33
|
+
recipient: Address;
|
|
34
|
+
destinationCaller: HexString;
|
|
35
|
+
minFinalityThreshold: number;
|
|
36
|
+
finalityThresholdExecuted: number;
|
|
37
|
+
messageBody: HexString;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Decoded CCTP V2 BurnMessage body — the message-body shape used for USDC
|
|
41
|
+
* bridging. `hookData` is empty (`"0x"`) for standard transfers.
|
|
42
|
+
*/
|
|
43
|
+
export type CctpV2BurnMessageBody = Readonly<{
|
|
44
|
+
version: number;
|
|
45
|
+
burnToken: Address;
|
|
46
|
+
mintRecipient: Address;
|
|
47
|
+
amount: bigint;
|
|
48
|
+
messageSender: Address;
|
|
49
|
+
maxFee: bigint;
|
|
50
|
+
feeExecuted: bigint;
|
|
51
|
+
expirationBlock: bigint;
|
|
52
|
+
hookData: HexString;
|
|
53
|
+
}>;
|
|
54
|
+
/** Header + burn-body decoded together in one pass. */
|
|
55
|
+
export type CctpV2Message = Readonly<CctpV2Header & {
|
|
56
|
+
decodedBody: CctpV2BurnMessageBody;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Decode the outer CCTP V2 header and return it alongside the embedded
|
|
60
|
+
* `messageBody`. Does not inspect the body contents — use {@link decodeCctpV2BurnBody}
|
|
61
|
+
* for that.
|
|
62
|
+
*
|
|
63
|
+
* @throws if `message` is shorter than the 148-byte header
|
|
64
|
+
* @throws if the header version byte is not {@link CCTP_V2_HEADER_VERSION}
|
|
65
|
+
*/
|
|
66
|
+
export declare const decodeCctpV2Header: (message: HexString) => CctpV2Header;
|
|
67
|
+
/**
|
|
68
|
+
* Decode a CCTP V2 BurnMessage body.
|
|
69
|
+
*
|
|
70
|
+
* @throws if `body` is shorter than the 228-byte fixed portion
|
|
71
|
+
* @throws if the body version byte is not {@link CCTP_V2_BURN_BODY_VERSION}
|
|
72
|
+
*/
|
|
73
|
+
export declare const decodeCctpV2BurnBody: (body: HexString) => CctpV2BurnMessageBody;
|
|
74
|
+
/**
|
|
75
|
+
* Convenience: decode the outer header and the embedded burn body in a
|
|
76
|
+
* single call. Throws the same errors as {@link decodeCctpV2Header} and
|
|
77
|
+
* {@link decodeCctpV2BurnBody}.
|
|
78
|
+
*/
|
|
79
|
+
export declare const decodeCctpV2Message: (message: HexString) => CctpV2Message;
|
|
80
|
+
//# sourceMappingURL=cctp.message.decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cctp.message.decoder.d.ts","sourceRoot":"","sources":["../../../src/integrations/cctp/cctp.message.decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,GAAa,CAAC;AAEjD,+DAA+D;AAC/D,eAAO,MAAM,yBAAyB,GAAa,CAAC;AA+BpD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,SAAS,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,WAAW,EAAE,SAAS,CAAC;CAC1B,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;CACvB,CAAC,CAAC;AAEH,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG,QAAQ,CAChC,YAAY,GAAG;IACX,WAAW,EAAE,qBAAqB,CAAC;CACtC,CACJ,CAAC;AASF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,YAAa,SAAS,KAAG,YA+BvD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,SAAU,SAAS,KAAG,qBA2BtD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,YAAa,SAAS,KAAG,aAIxD,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as CctpTokenMessengerV2Abi } from "./token.messenger.v2.abi";
|
|
2
2
|
export { default as CctpMessageTransmitterV2Abi } from "./message.transmitter.v2.abi";
|
|
3
3
|
export * from "./cctp.v2";
|
|
4
|
+
export * from "./cctp.message.decoder";
|
|
5
|
+
export * from "./cctp.iris.api";
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/cctp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AACtF,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/cctp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AACtF,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "./dist/index.cjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "1.4.
|
|
7
|
+
"version": "1.4.32",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"src",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@uniswap/v4-sdk": "1.21.2",
|
|
38
38
|
"axios": "1.7.2",
|
|
39
39
|
"ethers": "5.7",
|
|
40
|
-
"viem": "2.33.2"
|
|
40
|
+
"viem": "2.33.2",
|
|
41
|
+
"zod": "4.3.6"
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for Circle's iris-api V2 — the attestation service that watches
|
|
3
|
+
* CCTP V2 `depositForBurn` events and publishes signed attestations that
|
|
4
|
+
* the destination chain needs to mint USDC via `receiveMessage`.
|
|
5
|
+
*
|
|
6
|
+
* Only the production endpoint (https://iris-api.circle.com) is supported.
|
|
7
|
+
* Sandbox is intentionally omitted — we do not bridge via sandbox, and
|
|
8
|
+
* keeping a single base URL removes a class of foot-guns.
|
|
9
|
+
*
|
|
10
|
+
* API reference: https://developers.circle.com/stablecoins/iris-api-reference
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getAddress } from "viem";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import type { Address } from "viem";
|
|
16
|
+
import type { HexString } from "../../types";
|
|
17
|
+
import { getCctpV2Domain } from "./cctp.v2";
|
|
18
|
+
|
|
19
|
+
export const CCTP_IRIS_API_BASE = "https://iris-api.circle.com" as const;
|
|
20
|
+
|
|
21
|
+
// ─── Shared refinements ──────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const HexSchema = z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^0x[0-9a-fA-F]*$/, "Expected a 0x-prefixed hex string")
|
|
26
|
+
.transform((v) => v as HexString);
|
|
27
|
+
|
|
28
|
+
const AddressSchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^0x[0-9a-fA-F]{40}$/, "Expected a 20-byte hex address")
|
|
31
|
+
.transform((v): Address => getAddress(v));
|
|
32
|
+
|
|
33
|
+
/** iris-api serialises large integers as JSON strings. Coerce them to bigint. */
|
|
34
|
+
const BigIntStringSchema = z
|
|
35
|
+
.string()
|
|
36
|
+
.regex(/^-?\d+$/, "Expected a decimal integer as string")
|
|
37
|
+
.transform((v) => BigInt(v));
|
|
38
|
+
|
|
39
|
+
/** iris-api serialises small integers as strings too. Coerce them to number. */
|
|
40
|
+
const NumberStringSchema = z
|
|
41
|
+
.string()
|
|
42
|
+
.regex(/^-?\d+$/, "Expected a decimal integer as string")
|
|
43
|
+
.transform((v) => {
|
|
44
|
+
const n = Number(v);
|
|
45
|
+
if (!Number.isSafeInteger(n)) {
|
|
46
|
+
throw new Error(`iris-api returned non-safe integer: ${v}`);
|
|
47
|
+
}
|
|
48
|
+
return n;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ─── Decoded body ────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export const IrisDecodedBurnBodySchema = z
|
|
54
|
+
.object({
|
|
55
|
+
burnToken: AddressSchema,
|
|
56
|
+
mintRecipient: AddressSchema,
|
|
57
|
+
amount: BigIntStringSchema,
|
|
58
|
+
messageSender: AddressSchema,
|
|
59
|
+
maxFee: BigIntStringSchema,
|
|
60
|
+
feeExecuted: BigIntStringSchema,
|
|
61
|
+
expirationBlock: BigIntStringSchema,
|
|
62
|
+
hookData: HexSchema.nullable(),
|
|
63
|
+
})
|
|
64
|
+
.transform((v) => Object.freeze(v));
|
|
65
|
+
|
|
66
|
+
export type IrisDecodedBurnBody = z.infer<typeof IrisDecodedBurnBodySchema>;
|
|
67
|
+
|
|
68
|
+
// ─── Decoded envelope ────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export const IrisDecodedMessageSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
sourceDomain: NumberStringSchema,
|
|
73
|
+
destinationDomain: NumberStringSchema,
|
|
74
|
+
nonce: HexSchema,
|
|
75
|
+
sender: AddressSchema,
|
|
76
|
+
recipient: AddressSchema,
|
|
77
|
+
destinationCaller: HexSchema,
|
|
78
|
+
minFinalityThreshold: NumberStringSchema,
|
|
79
|
+
finalityThresholdExecuted: NumberStringSchema,
|
|
80
|
+
messageBody: HexSchema,
|
|
81
|
+
decodedMessageBody: IrisDecodedBurnBodySchema.nullable(),
|
|
82
|
+
})
|
|
83
|
+
.transform((v) => Object.freeze(v));
|
|
84
|
+
|
|
85
|
+
export type IrisDecodedMessage = z.infer<typeof IrisDecodedMessageSchema>;
|
|
86
|
+
|
|
87
|
+
// ─── Top-level message ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Possible values of the `status` field. Circle does not publish the full
|
|
91
|
+
* enum, so we accept any string and surface the observed common values as
|
|
92
|
+
* a type hint. Known values at time of writing:
|
|
93
|
+
* - `complete` — attestation signed and available
|
|
94
|
+
* - `pending_confirmations` — still waiting for source-chain finality
|
|
95
|
+
*/
|
|
96
|
+
export type IrisMessageStatus = "complete" | "pending_confirmations" | (string & {});
|
|
97
|
+
|
|
98
|
+
export const IrisMessageSchema = z
|
|
99
|
+
.object({
|
|
100
|
+
attestation: HexSchema.nullable(),
|
|
101
|
+
message: HexSchema.nullable(),
|
|
102
|
+
eventNonce: HexSchema,
|
|
103
|
+
cctpVersion: z.number().int(),
|
|
104
|
+
status: z.string() as z.ZodType<IrisMessageStatus>,
|
|
105
|
+
decodedMessage: IrisDecodedMessageSchema.nullable(),
|
|
106
|
+
delayReason: z.string().nullable(),
|
|
107
|
+
})
|
|
108
|
+
.transform((v) => Object.freeze(v));
|
|
109
|
+
|
|
110
|
+
export type IrisMessage = z.infer<typeof IrisMessageSchema>;
|
|
111
|
+
|
|
112
|
+
const IrisMessagesEnvelopeSchema = z.object({
|
|
113
|
+
messages: z.array(IrisMessageSchema).default([]),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
export type FetchCctpMessagesArgs = Readonly<{
|
|
119
|
+
sourceChainId: number;
|
|
120
|
+
txHash: HexString;
|
|
121
|
+
}>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Fetch all CCTP V2 messages produced by a source-chain transaction.
|
|
125
|
+
*
|
|
126
|
+
* A single `depositForBurn` emits one message, but the API returns an
|
|
127
|
+
* array to accommodate batched / compound transactions. Returns `[]` when
|
|
128
|
+
* iris has not yet indexed the tx.
|
|
129
|
+
*
|
|
130
|
+
* @throws if `sourceChainId` is not in the SDK's CCTP V2 domain registry
|
|
131
|
+
* @throws if iris responds with a non-2xx status
|
|
132
|
+
* @throws if the response body fails schema validation (e.g., new iris shape)
|
|
133
|
+
*/
|
|
134
|
+
export const fetchCctpMessages = async ({
|
|
135
|
+
sourceChainId,
|
|
136
|
+
txHash,
|
|
137
|
+
}: FetchCctpMessagesArgs): Promise<readonly IrisMessage[]> => {
|
|
138
|
+
// Throws if the chain is not CCTP V2 — preserves the existing SDK error.
|
|
139
|
+
const sourceDomain = getCctpV2Domain(sourceChainId);
|
|
140
|
+
|
|
141
|
+
const url = `${CCTP_IRIS_API_BASE}/v2/messages/${sourceDomain}?transactionHash=${txHash}`;
|
|
142
|
+
const response = await fetch(url);
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const body = await response.text().catch(() => "");
|
|
146
|
+
throw new Error(`iris-api returned ${response.status} ${response.statusText}: ${body}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const json: unknown = await response.json();
|
|
150
|
+
const parsed = IrisMessagesEnvelopeSchema.parse(json);
|
|
151
|
+
return Object.freeze(parsed.messages);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convenience: fetch the first message for a source-chain tx, or `null`
|
|
156
|
+
* if iris has no messages indexed for that tx.
|
|
157
|
+
*
|
|
158
|
+
* Use {@link fetchCctpMessages} directly when a single tx may have burned
|
|
159
|
+
* for multiple destinations (rare — CCTP V2 `depositForBurn` is 1:1).
|
|
160
|
+
*/
|
|
161
|
+
export const fetchCctpMessage = async (args: FetchCctpMessagesArgs): Promise<IrisMessage | null> => {
|
|
162
|
+
const messages = await fetchCctpMessages(args);
|
|
163
|
+
return messages[0] ?? null;
|
|
164
|
+
};
|