@provablehq/aleo-bridge-sdk 0.8.0-rc.0
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/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/agent/index.d.ts +22 -0
- package/dist/agent/index.js +7 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/chunk-KG3LFIU5.js +61 -0
- package/dist/chunk-KG3LFIU5.js.map +1 -0
- package/dist/createBridgeClient-CjHY-JvW.d.ts +779 -0
- package/dist/index.d.ts +421 -0
- package/dist/index.js +1627 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +26 -0
- package/dist/mcp/index.js +13 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1627 @@
|
|
|
1
|
+
// src/clients/createBridgeClient.ts
|
|
2
|
+
import { createClient, createTransport } from "@provablehq/veil-core";
|
|
3
|
+
|
|
4
|
+
// src/actions/protocolDiscovery.ts
|
|
5
|
+
function getProtocolAssets(registry, params = {}) {
|
|
6
|
+
const chains2 = new Map(registry.chains.map((chain) => [chain.id, chain]));
|
|
7
|
+
const chainId = params.chainId?.toLowerCase();
|
|
8
|
+
const symbol = params.symbol?.toLowerCase();
|
|
9
|
+
return registry.assets.filter((asset) => {
|
|
10
|
+
const chain = chains2.get(asset.chainId);
|
|
11
|
+
return (params.environment == null || chain?.environment === params.environment) && (chainId == null || asset.chainId.toLowerCase() === chainId) && (symbol == null || asset.symbol.toLowerCase() === symbol);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function getProtocolRoutes(registry, params = {}) {
|
|
15
|
+
const assets2 = new Map(registry.assets.map((asset) => [asset.id, asset]));
|
|
16
|
+
const sourceChainId = params.sourceChainId?.toLowerCase();
|
|
17
|
+
const destinationChainId = params.destinationChainId?.toLowerCase();
|
|
18
|
+
const symbol = params.symbol?.toLowerCase();
|
|
19
|
+
return registry.routes.filter((route2) => {
|
|
20
|
+
const source = assets2.get(route2.sourceAssetId);
|
|
21
|
+
const destination = assets2.get(route2.destinationAssetId);
|
|
22
|
+
return (params.includeUnavailable === true || route2.availability !== "disabled") && (params.environment == null || route2.environment === params.environment) && (params.protocol == null || route2.protocol === params.protocol) && (sourceChainId == null || source.chainId.toLowerCase() === sourceChainId) && (destinationChainId == null || destination.chainId.toLowerCase() === destinationChainId) && (symbol == null || source.symbol.toLowerCase() === symbol || destination.symbol.toLowerCase() === symbol);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/errors/bridgeErrors.ts
|
|
27
|
+
var BridgeError = class extends Error {
|
|
28
|
+
constructor(message, options) {
|
|
29
|
+
super(message, options);
|
|
30
|
+
this.name = "BridgeError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/utils/units.ts
|
|
35
|
+
function parseDecimalAmount(amount, decimals) {
|
|
36
|
+
const match = /^(\d+)(?:\.(\d+))?$/.exec(amount.trim());
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new BridgeError(`Invalid decimal amount "${amount}"`);
|
|
39
|
+
}
|
|
40
|
+
const whole = match[1];
|
|
41
|
+
const frac = match[2] ?? "";
|
|
42
|
+
if (frac.length > decimals) {
|
|
43
|
+
throw new BridgeError(
|
|
44
|
+
`Amount "${amount}" has ${frac.length} fractional digits but the asset supports ${decimals}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return BigInt(whole + frac.padEnd(decimals, "0"));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/actions/prepareTransfer.ts
|
|
51
|
+
function executor(chain) {
|
|
52
|
+
if (chain.family === "aleo") return "aleo-wallet";
|
|
53
|
+
if (chain.family === "evm") return "evm-wallet";
|
|
54
|
+
return "solana-wallet";
|
|
55
|
+
}
|
|
56
|
+
function xreserveSteps(route2, source, destination, sourceChain, destinationChain, mintMode) {
|
|
57
|
+
if (sourceChain.family === "evm" && destinationChain.family === "aleo") {
|
|
58
|
+
return [
|
|
59
|
+
{ key: "source-approval", kind: "approve", chainId: source.chainId, executor: "evm-wallet", description: `Approve the Circle xReserve contract to spend ${source.symbol}.`, irreversible: false },
|
|
60
|
+
{ key: "source-deposit", kind: "deposit", chainId: source.chainId, executor: "evm-wallet", description: `Deposit ${source.symbol} into Circle xReserve for Aleo.`, irreversible: true },
|
|
61
|
+
{ key: "deposit-attestation", kind: "wait-attestation", executor: "protocol", description: "Wait for Circle to attest the confirmed reserve deposit.", irreversible: false },
|
|
62
|
+
{ key: "destination-mint", kind: "mint", chainId: destination.chainId, executor: mintMode === "private" ? "aleo-wallet" : "protocol", description: mintMode === "private" ? `Submit private_mint to mint attested ${destination.symbol} through the shielded wrapper.` : `Wait for attested ${destination.symbol} to mint as an Aleo ${mintMode === "record" ? "record" : "public balance"}.`, irreversible: false }
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
if (sourceChain.family === "aleo" && destinationChain.family === "evm") {
|
|
66
|
+
return [
|
|
67
|
+
{ key: "source-burn", kind: "burn", chainId: source.chainId, executor: "aleo-wallet", description: `Burn ${source.symbol} and create the xReserve withdrawal intent; private burn is the default.`, irreversible: true },
|
|
68
|
+
{ key: "withdrawal-attestation", kind: "wait-attestation", executor: "protocol", description: "Wait for the Aleo burn attestation service to forward the accepted burn to Circle.", irreversible: false },
|
|
69
|
+
{ key: "destination-withdrawal", kind: "withdraw", chainId: destination.chainId, executor: "protocol", description: `Wait for Circle to release ${destination.symbol} to the native recipient.`, irreversible: false },
|
|
70
|
+
{ key: "destination-confirmation", kind: "confirm-delivery", chainId: destination.chainId, executor: "protocol", description: "Confirm the destination USDC balance change.", irreversible: false }
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
throw new BridgeError(`Unsupported xReserve route shape: ${route2.id}`);
|
|
74
|
+
}
|
|
75
|
+
function hyperlaneSteps(source, destination, sourceChain, destinationChain) {
|
|
76
|
+
const steps = [];
|
|
77
|
+
if (source.kind === "token" && sourceChain.family !== "aleo") {
|
|
78
|
+
steps.push({
|
|
79
|
+
key: "source-approval",
|
|
80
|
+
kind: "approve",
|
|
81
|
+
chainId: source.chainId,
|
|
82
|
+
executor: executor(sourceChain),
|
|
83
|
+
description: `Approve the Hyperlane Warp Route to spend ${source.symbol}.`,
|
|
84
|
+
irreversible: false
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
steps.push(
|
|
88
|
+
{ key: "source-dispatch", kind: "dispatch", chainId: source.chainId, executor: executor(sourceChain), description: `Dispatch ${source.symbol} through its Hyperlane Warp Route.`, irreversible: true },
|
|
89
|
+
{ key: "message-delivery", kind: "wait-delivery", executor: "protocol", description: "Wait for the Hyperlane message to be relayed and processed.", irreversible: false },
|
|
90
|
+
{ key: "destination-confirmation", kind: "confirm-delivery", chainId: destination.chainId, executor: "protocol", description: `Confirm relayer delivery of ${destination.symbol} on the destination chain.`, irreversible: false }
|
|
91
|
+
);
|
|
92
|
+
return steps;
|
|
93
|
+
}
|
|
94
|
+
function prepareTransfer(registry, params) {
|
|
95
|
+
const route2 = registry.routes.find((entry) => entry.id === params.routeId);
|
|
96
|
+
if (!route2) throw new BridgeError(`Unknown bridge route: ${params.routeId}`);
|
|
97
|
+
if (route2.availability === "disabled") throw new BridgeError(`Bridge route is disabled: ${params.routeId}`);
|
|
98
|
+
const sourceAsset = registry.assets.find((asset) => asset.id === route2.sourceAssetId);
|
|
99
|
+
const destinationAsset = registry.assets.find((asset) => asset.id === route2.destinationAssetId);
|
|
100
|
+
const sourceChain = registry.chains.find((chain) => chain.id === sourceAsset.chainId);
|
|
101
|
+
const destinationChain = registry.chains.find((chain) => chain.id === destinationAsset.chainId);
|
|
102
|
+
if (params.privateRecipient === true && params.mintMode != null && params.mintMode !== "private") {
|
|
103
|
+
throw new BridgeError("privateRecipient conflicts with the selected mintMode");
|
|
104
|
+
}
|
|
105
|
+
const mintMode = params.mintMode ?? (params.privateRecipient === true ? "private" : "public");
|
|
106
|
+
if (params.privateMintSecretNonce != null && mintMode !== "private") {
|
|
107
|
+
throw new BridgeError("privateMintSecretNonce is only valid with private mint mode");
|
|
108
|
+
}
|
|
109
|
+
const privateMintSecretNonce = params.privateMintSecretNonce ?? "0scalar";
|
|
110
|
+
if (mintMode === "private" && !/^(0|[1-9][0-9]*)scalar$/.test(privateMintSecretNonce)) {
|
|
111
|
+
throw new BridgeError("privateMintSecretNonce must be a non-negative decimal Aleo scalar literal such as 0scalar");
|
|
112
|
+
}
|
|
113
|
+
if ((params.mintMode != null || params.privateRecipient === true) && destinationChain.family !== "aleo") {
|
|
114
|
+
throw new BridgeError("Aleo mint mode is only valid when the destination chain is Aleo");
|
|
115
|
+
}
|
|
116
|
+
if (route2.protocol !== "xreserve" && mintMode !== "public") {
|
|
117
|
+
throw new BridgeError("record and private mint modes are only supported by xReserve routes");
|
|
118
|
+
}
|
|
119
|
+
const atomic = parseDecimalAmount(params.amount, sourceAsset.decimals);
|
|
120
|
+
if (atomic <= 0n) throw new BridgeError("Bridge transfer amount must be greater than zero");
|
|
121
|
+
parseDecimalAmount(params.amount, destinationAsset.decimals);
|
|
122
|
+
if (destinationAsset.addressValidationRegex) {
|
|
123
|
+
const regex = new RegExp(destinationAsset.addressValidationRegex);
|
|
124
|
+
if (!regex.test(params.recipient)) {
|
|
125
|
+
throw new BridgeError(`Recipient does not match ${destinationAsset.chainId} address format`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const steps = route2.protocol === "xreserve" ? xreserveSteps(route2, sourceAsset, destinationAsset, sourceChain, destinationChain, mintMode) : hyperlaneSteps(sourceAsset, destinationAsset, sourceChain, destinationChain);
|
|
129
|
+
const fees = [];
|
|
130
|
+
return {
|
|
131
|
+
registryVersion: registry.version,
|
|
132
|
+
protocol: route2.protocol,
|
|
133
|
+
route: route2,
|
|
134
|
+
sourceAsset,
|
|
135
|
+
destinationAsset,
|
|
136
|
+
amountIn: params.amount,
|
|
137
|
+
// Both current protocols preserve display units before live fee deduction.
|
|
138
|
+
// Omit a promise about net output until fee quoting is implemented.
|
|
139
|
+
recipient: params.recipient,
|
|
140
|
+
...params.sender == null ? {} : { sender: params.sender },
|
|
141
|
+
mintMode,
|
|
142
|
+
...mintMode === "private" ? { privateMintSecretNonce } : {},
|
|
143
|
+
privateRecipient: mintMode === "private",
|
|
144
|
+
quote: {
|
|
145
|
+
routeId: route2.id,
|
|
146
|
+
protocol: route2.protocol,
|
|
147
|
+
amountIn: params.amount,
|
|
148
|
+
fees,
|
|
149
|
+
status: "not-queried"
|
|
150
|
+
},
|
|
151
|
+
fees,
|
|
152
|
+
steps
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/actions/evmHyperlane.ts
|
|
157
|
+
import {
|
|
158
|
+
decodeEventLog,
|
|
159
|
+
decodeFunctionResult,
|
|
160
|
+
encodeFunctionData,
|
|
161
|
+
getAddress,
|
|
162
|
+
isAddress,
|
|
163
|
+
isHash,
|
|
164
|
+
parseAbi,
|
|
165
|
+
zeroAddress
|
|
166
|
+
} from "viem";
|
|
167
|
+
var WARP_ROUTE_ABI = parseAbi([
|
|
168
|
+
"function quoteTransferRemote(uint32 destination, bytes32 recipient, uint256 amount) view returns ((address token, uint256 amount)[] quotes)",
|
|
169
|
+
"function transferRemote(uint32 destination, bytes32 recipient, uint256 amount) payable returns (bytes32 messageId)"
|
|
170
|
+
]);
|
|
171
|
+
var ERC20_ABI = parseAbi([
|
|
172
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
173
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
174
|
+
]);
|
|
175
|
+
var DISPATCH_ID_ABI = parseAbi(["event DispatchId(bytes32 indexed messageId)"]);
|
|
176
|
+
function isHexOfBytes(value, bytes) {
|
|
177
|
+
return new RegExp(`^0x[0-9a-fA-F]{${bytes * 2}}$`).test(value);
|
|
178
|
+
}
|
|
179
|
+
function routeMetadata(registry, plan) {
|
|
180
|
+
if (plan.protocol !== "hyperlane" || plan.route.protocol !== "hyperlane") {
|
|
181
|
+
throw new BridgeError("Ethereum Hyperlane actions require a Hyperlane transfer plan");
|
|
182
|
+
}
|
|
183
|
+
if (plan.registryVersion !== registry.version) {
|
|
184
|
+
throw new BridgeError(`Transfer plan uses registry ${plan.registryVersion}; expected ${registry.version}`);
|
|
185
|
+
}
|
|
186
|
+
const route2 = registry.routes.find((entry) => entry.id === plan.route.id);
|
|
187
|
+
if (!route2 || route2.protocol !== "hyperlane") {
|
|
188
|
+
throw new BridgeError(`Hyperlane route is not present in the configured registry: ${plan.route.id}`);
|
|
189
|
+
}
|
|
190
|
+
if (route2.sourceAssetId !== plan.sourceAsset.id || route2.destinationAssetId !== plan.destinationAsset.id) {
|
|
191
|
+
throw new BridgeError(`Transfer plan assets do not match configured route: ${route2.id}`);
|
|
192
|
+
}
|
|
193
|
+
if (route2.availability !== "active") {
|
|
194
|
+
throw new BridgeError(`Hyperlane route is not executable: ${route2.id}`);
|
|
195
|
+
}
|
|
196
|
+
const metadata2 = route2.metadata;
|
|
197
|
+
if (!metadata2) throw new BridgeError(`Hyperlane route metadata is missing: ${plan.route.id}`);
|
|
198
|
+
const routerAddress = metadata2.routerAddress;
|
|
199
|
+
const sourceChainId = metadata2.sourceChainId;
|
|
200
|
+
const destinationDomain = metadata2.destinationDomain;
|
|
201
|
+
const routerType = metadata2.routerType;
|
|
202
|
+
const tokenAddress = metadata2.tokenAddress;
|
|
203
|
+
const destinationRouter = metadata2.destinationRouter;
|
|
204
|
+
const mailboxAddress = metadata2.mailboxAddress;
|
|
205
|
+
const interchainGasPaymaster = metadata2.interchainGasPaymaster;
|
|
206
|
+
const interchainSecurityModule = metadata2.interchainSecurityModule;
|
|
207
|
+
const registryCommit = metadata2.registryCommit;
|
|
208
|
+
if (typeof routerAddress !== "string" || !isAddress(routerAddress)) {
|
|
209
|
+
throw new BridgeError(`Hyperlane route has an invalid routerAddress: ${plan.route.id}`);
|
|
210
|
+
}
|
|
211
|
+
if (!Number.isInteger(sourceChainId) || typeof sourceChainId !== "number" || sourceChainId <= 0) {
|
|
212
|
+
throw new BridgeError(`Hyperlane route has an invalid sourceChainId: ${plan.route.id}`);
|
|
213
|
+
}
|
|
214
|
+
if (!Number.isInteger(destinationDomain) || typeof destinationDomain !== "number" || destinationDomain < 0 || destinationDomain > 4294967295) {
|
|
215
|
+
throw new BridgeError(`Hyperlane route has an invalid destinationDomain: ${plan.route.id}`);
|
|
216
|
+
}
|
|
217
|
+
if (routerType !== "native" && routerType !== "collateral") {
|
|
218
|
+
throw new BridgeError(`Hyperlane route has an invalid routerType: ${plan.route.id}`);
|
|
219
|
+
}
|
|
220
|
+
if (routerType === "collateral" && (typeof tokenAddress !== "string" || !isAddress(tokenAddress))) {
|
|
221
|
+
throw new BridgeError(`Collateral Hyperlane route has an invalid tokenAddress: ${plan.route.id}`);
|
|
222
|
+
}
|
|
223
|
+
if (typeof destinationRouter !== "string" || destinationRouter.length === 0) {
|
|
224
|
+
throw new BridgeError(`Hyperlane route has an invalid destinationRouter: ${plan.route.id}`);
|
|
225
|
+
}
|
|
226
|
+
if (typeof mailboxAddress !== "string" || !isAddress(mailboxAddress)) {
|
|
227
|
+
throw new BridgeError(`Hyperlane route has an invalid mailboxAddress: ${plan.route.id}`);
|
|
228
|
+
}
|
|
229
|
+
if (typeof interchainGasPaymaster !== "string" || !isAddress(interchainGasPaymaster)) {
|
|
230
|
+
throw new BridgeError(`Hyperlane route has an invalid interchainGasPaymaster: ${plan.route.id}`);
|
|
231
|
+
}
|
|
232
|
+
if (typeof interchainSecurityModule !== "string" || !isAddress(interchainSecurityModule)) {
|
|
233
|
+
throw new BridgeError(`Hyperlane route has an invalid interchainSecurityModule: ${plan.route.id}`);
|
|
234
|
+
}
|
|
235
|
+
if (typeof registryCommit !== "string" || !/^[0-9a-f]{40}$/i.test(registryCommit)) {
|
|
236
|
+
throw new BridgeError(`Hyperlane route has an invalid registryCommit: ${plan.route.id}`);
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
routerAddress: getAddress(routerAddress),
|
|
240
|
+
sourceChainId,
|
|
241
|
+
destinationDomain,
|
|
242
|
+
routerType,
|
|
243
|
+
...typeof tokenAddress === "string" && isAddress(tokenAddress) ? { tokenAddress: getAddress(tokenAddress) } : {},
|
|
244
|
+
destinationRouter,
|
|
245
|
+
mailboxAddress: getAddress(mailboxAddress),
|
|
246
|
+
interchainGasPaymaster: getAddress(interchainGasPaymaster),
|
|
247
|
+
interchainSecurityModule: getAddress(interchainSecurityModule),
|
|
248
|
+
registryCommit,
|
|
249
|
+
requiresApprovalReset: metadata2.requiresApprovalReset === true
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function validateRecipient(recipientBytes32) {
|
|
253
|
+
if (!isHexOfBytes(recipientBytes32, 32)) {
|
|
254
|
+
throw new BridgeError("Hyperlane recipientBytes32 must contain exactly 32 bytes");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function rpcCall(executor2, to, data) {
|
|
258
|
+
const result = await executor2.request({
|
|
259
|
+
method: "eth_call",
|
|
260
|
+
params: [{ to, data }, "latest"]
|
|
261
|
+
});
|
|
262
|
+
if (typeof result !== "string" || !result.startsWith("0x")) {
|
|
263
|
+
throw new BridgeError("EVM executor returned an invalid eth_call result");
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
async function assertChain(executor2, expectedChainId) {
|
|
268
|
+
const result = await executor2.request({ method: "eth_chainId" });
|
|
269
|
+
if (typeof result !== "string" || !/^0x[0-9a-f]+$/i.test(result)) {
|
|
270
|
+
throw new BridgeError("EVM executor returned an invalid eth_chainId result");
|
|
271
|
+
}
|
|
272
|
+
const actual = Number(BigInt(result));
|
|
273
|
+
if (actual !== expectedChainId) {
|
|
274
|
+
throw new BridgeError(`EVM wallet is connected to chain ${actual}; expected ${expectedChainId}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function resolveAccount(executor2, plan) {
|
|
278
|
+
let account2 = executor2.account;
|
|
279
|
+
if (!account2) {
|
|
280
|
+
const result = await executor2.request({ method: "eth_accounts" });
|
|
281
|
+
if (!Array.isArray(result) || typeof result[0] !== "string" || !isAddress(result[0])) {
|
|
282
|
+
throw new BridgeError("EVM executor has no connected account");
|
|
283
|
+
}
|
|
284
|
+
account2 = getAddress(result[0]);
|
|
285
|
+
}
|
|
286
|
+
if (!isAddress(account2)) throw new BridgeError("EVM executor account is invalid");
|
|
287
|
+
const normalized = getAddress(account2);
|
|
288
|
+
if (plan.sender && (!isAddress(plan.sender) || getAddress(plan.sender) !== normalized)) {
|
|
289
|
+
throw new BridgeError(`Prepared sender ${plan.sender} does not match connected account ${normalized}`);
|
|
290
|
+
}
|
|
291
|
+
return normalized;
|
|
292
|
+
}
|
|
293
|
+
async function sendTransaction(executor2, transaction) {
|
|
294
|
+
const result = await executor2.request({ method: "eth_sendTransaction", params: [transaction] });
|
|
295
|
+
if (typeof result !== "string" || !isHash(result)) {
|
|
296
|
+
throw new BridgeError("EVM executor returned an invalid transaction hash");
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
async function waitForReceipt(executor2, hash, timeoutMs, pollingIntervalMs) {
|
|
301
|
+
const deadline = Date.now() + timeoutMs;
|
|
302
|
+
do {
|
|
303
|
+
const result = await executor2.request({ method: "eth_getTransactionReceipt", params: [hash] });
|
|
304
|
+
if (result != null && typeof result === "object") return result;
|
|
305
|
+
if (Date.now() >= deadline) return void 0;
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
|
|
307
|
+
} while (true);
|
|
308
|
+
}
|
|
309
|
+
function assertSuccessfulReceipt(receipt, hash) {
|
|
310
|
+
if (receipt.status === "0x0") throw new BridgeError(`EVM transaction reverted: ${hash}`);
|
|
311
|
+
}
|
|
312
|
+
function messageIdFromReceipt(receipt) {
|
|
313
|
+
for (const log of receipt.logs ?? []) {
|
|
314
|
+
try {
|
|
315
|
+
const signature = log.topics[0];
|
|
316
|
+
if (!signature) continue;
|
|
317
|
+
const decoded = decodeEventLog({
|
|
318
|
+
abi: DISPATCH_ID_ABI,
|
|
319
|
+
data: log.data,
|
|
320
|
+
topics: [signature, ...log.topics.slice(1)],
|
|
321
|
+
strict: false
|
|
322
|
+
});
|
|
323
|
+
const messageId = decoded.args.messageId;
|
|
324
|
+
if (decoded.eventName === "DispatchId" && messageId && isHash(messageId)) return messageId;
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
async function quoteEvmHyperlaneTransfer(registry, executor2, params) {
|
|
331
|
+
validateRecipient(params.recipientBytes32);
|
|
332
|
+
const metadata2 = routeMetadata(registry, params.plan);
|
|
333
|
+
await assertChain(executor2, metadata2.sourceChainId);
|
|
334
|
+
const sourceAsset = registry.assets.find((asset) => asset.id === params.plan.sourceAsset.id);
|
|
335
|
+
const amountAtomic = parseDecimalAmount(params.plan.amountIn, sourceAsset.decimals);
|
|
336
|
+
const data = encodeFunctionData({
|
|
337
|
+
abi: WARP_ROUTE_ABI,
|
|
338
|
+
functionName: "quoteTransferRemote",
|
|
339
|
+
args: [metadata2.destinationDomain, params.recipientBytes32, amountAtomic]
|
|
340
|
+
});
|
|
341
|
+
const encoded = await rpcCall(executor2, metadata2.routerAddress, data);
|
|
342
|
+
const quotes = decodeFunctionResult({
|
|
343
|
+
abi: WARP_ROUTE_ABI,
|
|
344
|
+
functionName: "quoteTransferRemote",
|
|
345
|
+
data: encoded
|
|
346
|
+
});
|
|
347
|
+
const nativeValueAtomic = quotes.filter((quote) => getAddress(quote.token) === zeroAddress).reduce((sum, quote) => sum + quote.amount, 0n);
|
|
348
|
+
if (metadata2.routerType === "native") {
|
|
349
|
+
if (nativeValueAtomic < amountAtomic) {
|
|
350
|
+
throw new BridgeError("Native Hyperlane quote does not cover the transfer amount");
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
routeId: params.plan.route.id,
|
|
354
|
+
routerAddress: metadata2.routerAddress,
|
|
355
|
+
sourceChainId: metadata2.sourceChainId,
|
|
356
|
+
destinationDomain: metadata2.destinationDomain,
|
|
357
|
+
recipientBytes32: params.recipientBytes32,
|
|
358
|
+
amountAtomic,
|
|
359
|
+
nativeValueAtomic,
|
|
360
|
+
nativeFeeAtomic: nativeValueAtomic - amountAtomic
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const tokenAddress = metadata2.tokenAddress;
|
|
364
|
+
const tokenAmountAtomic = quotes.filter((quote) => getAddress(quote.token) === tokenAddress).reduce((sum, quote) => sum + quote.amount, 0n);
|
|
365
|
+
if (tokenAmountAtomic < amountAtomic) {
|
|
366
|
+
throw new BridgeError("Collateral Hyperlane quote does not cover the transfer amount");
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
routeId: params.plan.route.id,
|
|
370
|
+
routerAddress: metadata2.routerAddress,
|
|
371
|
+
sourceChainId: metadata2.sourceChainId,
|
|
372
|
+
destinationDomain: metadata2.destinationDomain,
|
|
373
|
+
recipientBytes32: params.recipientBytes32,
|
|
374
|
+
amountAtomic,
|
|
375
|
+
nativeValueAtomic,
|
|
376
|
+
nativeFeeAtomic: nativeValueAtomic,
|
|
377
|
+
tokenAmountAtomic,
|
|
378
|
+
tokenAddress
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function executionReceipt(plan, status, id, quote, approvalTxIds, sourceTxId, messageId) {
|
|
382
|
+
return {
|
|
383
|
+
id,
|
|
384
|
+
protocol: "hyperlane",
|
|
385
|
+
status,
|
|
386
|
+
...sourceTxId ? { sourceTxId } : {},
|
|
387
|
+
...messageId ? { messageId } : {},
|
|
388
|
+
protocolState: {
|
|
389
|
+
routeId: plan.route.id,
|
|
390
|
+
approvalTxIds,
|
|
391
|
+
recipientBytes32: quote.recipientBytes32,
|
|
392
|
+
destinationDomain: quote.destinationDomain,
|
|
393
|
+
nativeValueAtomic: quote.nativeValueAtomic.toString(),
|
|
394
|
+
amountAtomic: quote.amountAtomic.toString()
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async function executeEvmHyperlaneTransfer(registry, executor2, params) {
|
|
399
|
+
const pollingIntervalMs = params.pollingIntervalMs ?? 1e3;
|
|
400
|
+
const confirmationTimeoutMs = params.confirmationTimeoutMs ?? 12e4;
|
|
401
|
+
if (!Number.isFinite(pollingIntervalMs) || pollingIntervalMs < 0) {
|
|
402
|
+
throw new BridgeError("pollingIntervalMs must be a non-negative finite number");
|
|
403
|
+
}
|
|
404
|
+
if (!Number.isFinite(confirmationTimeoutMs) || confirmationTimeoutMs < 0) {
|
|
405
|
+
throw new BridgeError("confirmationTimeoutMs must be a non-negative finite number");
|
|
406
|
+
}
|
|
407
|
+
const metadata2 = routeMetadata(registry, params.plan);
|
|
408
|
+
const quote = await quoteEvmHyperlaneTransfer(registry, executor2, params);
|
|
409
|
+
const account2 = await resolveAccount(executor2, params.plan);
|
|
410
|
+
const approvalTxIds = [];
|
|
411
|
+
if (metadata2.routerType === "collateral") {
|
|
412
|
+
const allowanceData = encodeFunctionData({
|
|
413
|
+
abi: ERC20_ABI,
|
|
414
|
+
functionName: "allowance",
|
|
415
|
+
args: [account2, metadata2.routerAddress]
|
|
416
|
+
});
|
|
417
|
+
const allowanceResult = await rpcCall(executor2, metadata2.tokenAddress, allowanceData);
|
|
418
|
+
const allowance2 = decodeFunctionResult({
|
|
419
|
+
abi: ERC20_ABI,
|
|
420
|
+
functionName: "allowance",
|
|
421
|
+
data: allowanceResult
|
|
422
|
+
});
|
|
423
|
+
const required = quote.tokenAmountAtomic;
|
|
424
|
+
const approveAndConfirm = async (amount) => {
|
|
425
|
+
const data = encodeFunctionData({
|
|
426
|
+
abi: ERC20_ABI,
|
|
427
|
+
functionName: "approve",
|
|
428
|
+
args: [metadata2.routerAddress, amount]
|
|
429
|
+
});
|
|
430
|
+
const hash = await sendTransaction(executor2, { from: account2, to: metadata2.tokenAddress, data });
|
|
431
|
+
approvalTxIds.push(hash);
|
|
432
|
+
const receipt = await waitForReceipt(executor2, hash, confirmationTimeoutMs, pollingIntervalMs);
|
|
433
|
+
if (!receipt) return false;
|
|
434
|
+
assertSuccessfulReceipt(receipt, hash);
|
|
435
|
+
return true;
|
|
436
|
+
};
|
|
437
|
+
if (allowance2 < required) {
|
|
438
|
+
if (allowance2 > 0n && metadata2.requiresApprovalReset) {
|
|
439
|
+
if (!await approveAndConfirm(0n)) {
|
|
440
|
+
return {
|
|
441
|
+
approvalTxIds,
|
|
442
|
+
receipt: executionReceipt(params.plan, "SOURCE_APPROVAL_PENDING", approvalTxIds.at(-1), quote, approvalTxIds)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (!await approveAndConfirm(required)) {
|
|
447
|
+
return {
|
|
448
|
+
approvalTxIds,
|
|
449
|
+
receipt: executionReceipt(params.plan, "SOURCE_APPROVAL_PENDING", approvalTxIds.at(-1), quote, approvalTxIds)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const transferData = encodeFunctionData({
|
|
455
|
+
abi: WARP_ROUTE_ABI,
|
|
456
|
+
functionName: "transferRemote",
|
|
457
|
+
args: [quote.destinationDomain, quote.recipientBytes32, quote.amountAtomic]
|
|
458
|
+
});
|
|
459
|
+
const sourceTxId = await sendTransaction(executor2, {
|
|
460
|
+
from: account2,
|
|
461
|
+
to: quote.routerAddress,
|
|
462
|
+
data: transferData,
|
|
463
|
+
value: `0x${quote.nativeValueAtomic.toString(16)}`
|
|
464
|
+
});
|
|
465
|
+
const sourceReceipt = await waitForReceipt(
|
|
466
|
+
executor2,
|
|
467
|
+
sourceTxId,
|
|
468
|
+
confirmationTimeoutMs,
|
|
469
|
+
pollingIntervalMs
|
|
470
|
+
);
|
|
471
|
+
if (!sourceReceipt) {
|
|
472
|
+
return {
|
|
473
|
+
approvalTxIds,
|
|
474
|
+
receipt: executionReceipt(params.plan, "SOURCE_CONFIRMING", sourceTxId, quote, approvalTxIds, sourceTxId)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
assertSuccessfulReceipt(sourceReceipt, sourceTxId);
|
|
478
|
+
const messageId = messageIdFromReceipt(sourceReceipt);
|
|
479
|
+
return {
|
|
480
|
+
approvalTxIds,
|
|
481
|
+
receipt: executionReceipt(
|
|
482
|
+
params.plan,
|
|
483
|
+
"DELIVERY_PENDING",
|
|
484
|
+
messageId ?? sourceTxId,
|
|
485
|
+
quote,
|
|
486
|
+
approvalTxIds,
|
|
487
|
+
sourceTxId,
|
|
488
|
+
messageId
|
|
489
|
+
)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/actions/evmXReserve.ts
|
|
494
|
+
import {
|
|
495
|
+
decodeEventLog as decodeEventLog2,
|
|
496
|
+
decodeFunctionResult as decodeFunctionResult2,
|
|
497
|
+
encodeFunctionData as encodeFunctionData2,
|
|
498
|
+
getAddress as getAddress3,
|
|
499
|
+
isAddress as isAddress3,
|
|
500
|
+
isHash as isHash2,
|
|
501
|
+
isHex as isHex2,
|
|
502
|
+
parseAbi as parseAbi2
|
|
503
|
+
} from "viem";
|
|
504
|
+
|
|
505
|
+
// src/utils/xreserve.ts
|
|
506
|
+
import {
|
|
507
|
+
encodeAbiParameters,
|
|
508
|
+
getAddress as getAddress2,
|
|
509
|
+
hexToBytes,
|
|
510
|
+
isAddress as isAddress2,
|
|
511
|
+
isHex,
|
|
512
|
+
keccak256,
|
|
513
|
+
padHex,
|
|
514
|
+
toHex
|
|
515
|
+
} from "viem";
|
|
516
|
+
var HOOK_DATA_BYTES = 65;
|
|
517
|
+
var BECH32_ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
518
|
+
function bech32Polymod(values) {
|
|
519
|
+
const generators = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
520
|
+
let checksum = 1;
|
|
521
|
+
for (const value of values) {
|
|
522
|
+
const top = checksum >>> 25;
|
|
523
|
+
checksum = (checksum & 33554431) << 5 ^ value;
|
|
524
|
+
for (let index = 0; index < 5; index++) if (top >>> index & 1) checksum ^= generators[index];
|
|
525
|
+
}
|
|
526
|
+
return checksum >>> 0;
|
|
527
|
+
}
|
|
528
|
+
function decodeAleoBech32m(address) {
|
|
529
|
+
const separator = address.lastIndexOf("1");
|
|
530
|
+
const prefix = address.slice(0, separator);
|
|
531
|
+
const encoded = address.slice(separator + 1);
|
|
532
|
+
const words = [...encoded].map((character) => BECH32_ALPHABET.indexOf(character));
|
|
533
|
+
if (prefix !== "aleo" || separator < 1 || words.some((word) => word < 0)) throw new Error("invalid encoding");
|
|
534
|
+
const expanded = [...prefix].map((character) => character.charCodeAt(0) >>> 5).concat([0], [...prefix].map((character) => character.charCodeAt(0) & 31), words);
|
|
535
|
+
if (bech32Polymod(expanded) !== 734539939) throw new Error("invalid checksum");
|
|
536
|
+
const payload = words.slice(0, -6);
|
|
537
|
+
const bytes = [];
|
|
538
|
+
let accumulator = 0;
|
|
539
|
+
let bits = 0;
|
|
540
|
+
for (const word of payload) {
|
|
541
|
+
accumulator = accumulator << 5 | word;
|
|
542
|
+
bits += 5;
|
|
543
|
+
while (bits >= 8) {
|
|
544
|
+
bits -= 8;
|
|
545
|
+
bytes.push(accumulator >>> bits & 255);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (bits >= 5 || (accumulator << 8 - bits & 255) !== 0) throw new Error("invalid padding");
|
|
549
|
+
return Uint8Array.from(bytes);
|
|
550
|
+
}
|
|
551
|
+
function aleoAddressToBytes32(address) {
|
|
552
|
+
try {
|
|
553
|
+
if (!address.startsWith("aleo1") || address.length !== 63) throw new Error("invalid prefix or length");
|
|
554
|
+
const bytes = decodeAleoBech32m(address);
|
|
555
|
+
if (bytes.length !== 32) throw new Error("invalid payload");
|
|
556
|
+
return toHex(bytes);
|
|
557
|
+
} catch (cause) {
|
|
558
|
+
throw new BridgeError(`Invalid Aleo recipient address: ${address}`, { cause });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function loadAleoSdk(environment) {
|
|
562
|
+
const moduleName = "@provablehq/sdk/dynamic.js";
|
|
563
|
+
try {
|
|
564
|
+
const sdk = await import(moduleName);
|
|
565
|
+
return sdk.loadNetwork(environment);
|
|
566
|
+
} catch (cause) {
|
|
567
|
+
throw new BridgeError("Private xReserve mints require the optional @provablehq/sdk package", { cause });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async function aleoProgramAddress(programId, environment) {
|
|
571
|
+
const sdk = await loadAleoSdk(environment);
|
|
572
|
+
return sdk.Address.fromProgramId(programId).to_string();
|
|
573
|
+
}
|
|
574
|
+
async function buildXReserveHookData(mode, recipient, environment, secretNonce = "0scalar") {
|
|
575
|
+
const bytes = new Uint8Array(HOOK_DATA_BYTES);
|
|
576
|
+
bytes[0] = mode === "public" ? 0 : mode === "record" ? 1 : 2;
|
|
577
|
+
if (mode === "private") {
|
|
578
|
+
const sdk = await loadAleoSdk(environment);
|
|
579
|
+
const bits = sdk.Plaintext.fromString(recipient).toBitsLe();
|
|
580
|
+
let scalar;
|
|
581
|
+
try {
|
|
582
|
+
scalar = sdk.Scalar.fromString(secretNonce);
|
|
583
|
+
} catch (cause) {
|
|
584
|
+
throw new BridgeError(`Invalid private mint secret nonce: ${secretNonce}`, { cause });
|
|
585
|
+
}
|
|
586
|
+
const commitment = new sdk.BHP256().commit(bits, scalar).toBytesLe();
|
|
587
|
+
if (commitment.length !== 32) throw new BridgeError("Private mint commitment must contain 32 bytes");
|
|
588
|
+
bytes.set(commitment, 1);
|
|
589
|
+
}
|
|
590
|
+
return toHex(bytes);
|
|
591
|
+
}
|
|
592
|
+
function calculateXReserveDepositNonce(sourceDomain, transactionHash, logIndex) {
|
|
593
|
+
const domain = encodeAbiParameters([{ type: "uint32" }], [sourceDomain]);
|
|
594
|
+
const index = encodeAbiParameters([{ type: "uint256" }], [BigInt(logIndex)]);
|
|
595
|
+
return keccak256(`0x${domain.slice(2)}${transactionHash.slice(2)}${index.slice(2)}`);
|
|
596
|
+
}
|
|
597
|
+
function uintBytes(value, bytes) {
|
|
598
|
+
if (value < 0n || value >= 1n << BigInt(bytes * 8)) throw new BridgeError(`Unsigned value does not fit in ${bytes} bytes`);
|
|
599
|
+
return hexToBytes(toHex(value, { size: bytes }));
|
|
600
|
+
}
|
|
601
|
+
function buildXReserveDepositPayload(params) {
|
|
602
|
+
if (!isHex(params.remoteToken, { strict: true }) || hexToBytes(params.remoteToken).length !== 32) throw new BridgeError("remoteToken must contain 32 bytes");
|
|
603
|
+
if (!isHex(params.remoteRecipient, { strict: true }) || hexToBytes(params.remoteRecipient).length !== 32) throw new BridgeError("remoteRecipient must contain 32 bytes");
|
|
604
|
+
if (!isHex(params.hookData, { strict: true }) || hexToBytes(params.hookData).length !== HOOK_DATA_BYTES) throw new BridgeError("hookData must contain 65 bytes");
|
|
605
|
+
if (!isAddress2(params.localToken) || !isAddress2(params.depositor)) throw new BridgeError("Payload EVM address is invalid");
|
|
606
|
+
const payload = new Uint8Array(305);
|
|
607
|
+
payload.set([90, 46, 10, 205, 0, 0, 0, 1], 0);
|
|
608
|
+
payload.set(uintBytes(params.amount, 32), 8);
|
|
609
|
+
payload.set(uintBytes(BigInt(params.remoteDomain), 4), 40);
|
|
610
|
+
payload.set(hexToBytes(params.remoteToken), 44);
|
|
611
|
+
payload.set(hexToBytes(params.remoteRecipient), 76);
|
|
612
|
+
payload.set(hexToBytes(padHex(getAddress2(params.localToken), { size: 32 })), 108);
|
|
613
|
+
payload.set(hexToBytes(padHex(getAddress2(params.depositor), { size: 32 })), 140);
|
|
614
|
+
payload.set(uintBytes(params.maxFee, 32), 172);
|
|
615
|
+
payload.set(hexToBytes(params.nonce), 204);
|
|
616
|
+
payload.set(uintBytes(BigInt(HOOK_DATA_BYTES), 4), 236);
|
|
617
|
+
payload.set(hexToBytes(params.hookData), 240);
|
|
618
|
+
return toHex(payload);
|
|
619
|
+
}
|
|
620
|
+
function calculateXReserveMessageHash(payload) {
|
|
621
|
+
return keccak256(payload);
|
|
622
|
+
}
|
|
623
|
+
function xReserveHexToAleoBytes(value, expectedBytes) {
|
|
624
|
+
if (!isHex(value, { strict: true })) throw new BridgeError("Aleo byte-array input must be prefixed hexadecimal");
|
|
625
|
+
const bytes = hexToBytes(value);
|
|
626
|
+
if (bytes.length !== expectedBytes) throw new BridgeError(`Aleo byte-array input must contain ${expectedBytes} bytes`);
|
|
627
|
+
return `[${[...bytes].map((byte) => `${byte}u8`).join(",")}]`;
|
|
628
|
+
}
|
|
629
|
+
function evmAddressToXReserveBytes32(address) {
|
|
630
|
+
if (!isAddress2(address)) throw new BridgeError(`Invalid Ethereum recipient address: ${address}`);
|
|
631
|
+
return padHex(getAddress2(address), { size: 32 });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/actions/evmXReserve.ts
|
|
635
|
+
var ERC20_ABI2 = parseAbi2([
|
|
636
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
637
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
638
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
639
|
+
]);
|
|
640
|
+
var XRESERVE_ABI = parseAbi2([
|
|
641
|
+
"function depositToRemote(uint256 value, uint32 remoteDomain, bytes32 remoteRecipient, address localToken, uint256 maxFee, bytes hookData)",
|
|
642
|
+
"event DepositedToRemote(address indexed localToken, uint256 value, address indexed localDepositor, bytes32 indexed remoteRecipient, uint32 remoteDomain, bytes32 remoteToken, uint256 maxFee, bytes hookData)"
|
|
643
|
+
]);
|
|
644
|
+
function metadata(registry, plan) {
|
|
645
|
+
if (plan.protocol !== "xreserve" || plan.route.protocol !== "xreserve") throw new BridgeError("xReserve actions require an xReserve transfer plan");
|
|
646
|
+
if (plan.registryVersion !== registry.version) throw new BridgeError(`Transfer plan uses registry ${plan.registryVersion}; expected ${registry.version}`);
|
|
647
|
+
const route2 = registry.routes.find((entry) => entry.id === plan.route.id);
|
|
648
|
+
if (!route2 || route2.availability !== "active") throw new BridgeError(`xReserve route is not executable: ${plan.route.id}`);
|
|
649
|
+
if (route2.sourceAssetId !== plan.sourceAsset.id || route2.destinationAssetId !== plan.destinationAsset.id) throw new BridgeError(`Transfer plan assets do not match configured route: ${route2.id}`);
|
|
650
|
+
const sourceChain = registry.chains.find((chain) => chain.id === plan.sourceAsset.chainId);
|
|
651
|
+
if (sourceChain?.family !== "evm" || plan.destinationAsset.chainId !== (route2.environment === "mainnet" ? "aleo" : "aleo-testnet")) {
|
|
652
|
+
throw new BridgeError("This action supports Ethereum-to-Aleo xReserve deposits only");
|
|
653
|
+
}
|
|
654
|
+
const raw = route2.metadata ?? {};
|
|
655
|
+
const xReserveContract = raw.xReserveContract;
|
|
656
|
+
const sourceChainId = raw.sourceChainId;
|
|
657
|
+
const sourceDomain = raw.sourceDomain;
|
|
658
|
+
const remoteDomain = raw.remoteDomain;
|
|
659
|
+
const remoteTokenBytes32 = raw.remoteTokenBytes32;
|
|
660
|
+
const minimumAmountAtomic = raw.minimumAmountAtomic;
|
|
661
|
+
const maxFeeAtomic = raw.maxFeeAtomic;
|
|
662
|
+
const bridgeProgram = raw.bridgeProgram;
|
|
663
|
+
const wrapperProgram = raw.wrapperProgram;
|
|
664
|
+
const attestationBaseUrl = raw.attestationBaseUrl;
|
|
665
|
+
if (typeof xReserveContract !== "string" || !isAddress3(xReserveContract)) throw new BridgeError(`xReserve contract is invalid: ${route2.id}`);
|
|
666
|
+
if (typeof sourceChainId !== "number" || !Number.isSafeInteger(sourceChainId) || sourceChainId <= 0) throw new BridgeError(`xReserve sourceChainId is invalid: ${route2.id}`);
|
|
667
|
+
if (typeof sourceDomain !== "number" || !Number.isInteger(sourceDomain) || sourceDomain < 0) throw new BridgeError(`xReserve sourceDomain is invalid: ${route2.id}`);
|
|
668
|
+
if (typeof remoteDomain !== "number" || !Number.isInteger(remoteDomain) || remoteDomain < 0) throw new BridgeError(`xReserve remoteDomain is invalid: ${route2.id}`);
|
|
669
|
+
if (typeof remoteTokenBytes32 !== "string" || !/^0x[0-9a-f]{64}$/i.test(remoteTokenBytes32)) throw new BridgeError(`xReserve remote token is invalid: ${route2.id}`);
|
|
670
|
+
if (typeof minimumAmountAtomic !== "string" || !/^\d+$/.test(minimumAmountAtomic)) throw new BridgeError(`xReserve minimum amount is invalid: ${route2.id}`);
|
|
671
|
+
if (typeof maxFeeAtomic !== "string" || !/^\d+$/.test(maxFeeAtomic)) throw new BridgeError(`xReserve max fee is invalid: ${route2.id}`);
|
|
672
|
+
if (typeof bridgeProgram !== "string" || !bridgeProgram.endsWith(".aleo")) throw new BridgeError(`xReserve bridge program is invalid: ${route2.id}`);
|
|
673
|
+
if (typeof wrapperProgram !== "string" || !wrapperProgram.endsWith(".aleo")) throw new BridgeError(`xReserve wrapper program is invalid: ${route2.id}`);
|
|
674
|
+
if (typeof attestationBaseUrl !== "string" || !attestationBaseUrl.startsWith("https://")) throw new BridgeError(`xReserve attestation URL is invalid: ${route2.id}`);
|
|
675
|
+
return { xReserveContract: getAddress3(xReserveContract), sourceChainId, sourceDomain, remoteDomain, remoteTokenBytes32, minimumAmountAtomic: BigInt(minimumAmountAtomic), maxFeeAtomic: BigInt(maxFeeAtomic), bridgeProgram, wrapperProgram, attestationBaseUrl };
|
|
676
|
+
}
|
|
677
|
+
async function rpc(executor2, method, params) {
|
|
678
|
+
return executor2.request({ method, ...params ? { params } : {} });
|
|
679
|
+
}
|
|
680
|
+
async function assertChain2(executor2, expected) {
|
|
681
|
+
const chain = await rpc(executor2, "eth_chainId");
|
|
682
|
+
if (typeof chain !== "string" || !/^0x[0-9a-f]+$/i.test(chain)) throw new BridgeError("EVM executor returned an invalid chain id");
|
|
683
|
+
if (Number(BigInt(chain)) !== expected) throw new BridgeError(`EVM wallet is connected to chain ${Number(BigInt(chain))}; expected ${expected}`);
|
|
684
|
+
}
|
|
685
|
+
async function account(executor2, plan) {
|
|
686
|
+
const value = executor2.account ?? (await rpc(executor2, "eth_accounts"))?.[0];
|
|
687
|
+
if (typeof value !== "string" || !isAddress3(value)) throw new BridgeError("EVM executor has no connected account");
|
|
688
|
+
const resolved = getAddress3(value);
|
|
689
|
+
if (plan.sender && (!isAddress3(plan.sender) || getAddress3(plan.sender) !== resolved)) throw new BridgeError(`Prepared sender ${plan.sender} does not match connected account ${resolved}`);
|
|
690
|
+
return resolved;
|
|
691
|
+
}
|
|
692
|
+
async function callUint(executor2, to, data, functionName) {
|
|
693
|
+
const result = await rpc(executor2, "eth_call", [{ to, data }, "latest"]);
|
|
694
|
+
if (typeof result !== "string" || !isHex2(result)) throw new BridgeError("EVM executor returned an invalid contract result");
|
|
695
|
+
return decodeFunctionResult2({ abi: ERC20_ABI2, functionName, data: result });
|
|
696
|
+
}
|
|
697
|
+
async function send(executor2, transaction) {
|
|
698
|
+
const hash = await rpc(executor2, "eth_sendTransaction", [transaction]);
|
|
699
|
+
if (typeof hash !== "string" || !isHash2(hash)) throw new BridgeError("EVM executor returned an invalid transaction hash");
|
|
700
|
+
return hash;
|
|
701
|
+
}
|
|
702
|
+
async function wait(executor2, hash, timeout, interval) {
|
|
703
|
+
const deadline = Date.now() + timeout;
|
|
704
|
+
do {
|
|
705
|
+
const result = await rpc(executor2, "eth_getTransactionReceipt", [hash]);
|
|
706
|
+
if (result && typeof result === "object") return result;
|
|
707
|
+
if (Date.now() >= deadline) return void 0;
|
|
708
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
709
|
+
} while (true);
|
|
710
|
+
}
|
|
711
|
+
function successful(receipt, hash) {
|
|
712
|
+
if (receipt.status === "0x0") throw new BridgeError(`EVM transaction reverted: ${hash}`);
|
|
713
|
+
}
|
|
714
|
+
async function quoteEvmXReserveTransfer(registry, executor2, params) {
|
|
715
|
+
const route2 = metadata(registry, params.plan);
|
|
716
|
+
await assertChain2(executor2, route2.sourceChainId);
|
|
717
|
+
const owner = await account(executor2, params.plan);
|
|
718
|
+
const token = params.plan.sourceAsset.locator?.value;
|
|
719
|
+
if (params.plan.sourceAsset.locator?.kind !== "evm-contract" || !token || !isAddress3(token)) throw new BridgeError("xReserve source token contract is missing");
|
|
720
|
+
const amountAtomic = parseDecimalAmount(params.plan.amountIn, params.plan.sourceAsset.decimals);
|
|
721
|
+
if (amountAtomic < route2.minimumAmountAtomic) throw new BridgeError(`xReserve minimum deposit is ${route2.minimumAmountAtomic} atomic units`);
|
|
722
|
+
const environment = params.plan.route.environment;
|
|
723
|
+
const hookData = await buildXReserveHookData(
|
|
724
|
+
params.plan.mintMode,
|
|
725
|
+
params.plan.recipient,
|
|
726
|
+
environment,
|
|
727
|
+
params.plan.privateMintSecretNonce ?? "0scalar"
|
|
728
|
+
);
|
|
729
|
+
const recipient = params.plan.mintMode === "private" ? await aleoProgramAddress(route2.wrapperProgram, environment) : params.plan.recipient;
|
|
730
|
+
const remoteRecipientBytes32 = aleoAddressToBytes32(recipient);
|
|
731
|
+
const balanceData = encodeFunctionData2({ abi: ERC20_ABI2, functionName: "balanceOf", args: [owner] });
|
|
732
|
+
const allowanceData = encodeFunctionData2({ abi: ERC20_ABI2, functionName: "allowance", args: [owner, route2.xReserveContract] });
|
|
733
|
+
const [balanceAtomic, allowanceAtomic] = await Promise.all([
|
|
734
|
+
callUint(executor2, getAddress3(token), balanceData, "balanceOf"),
|
|
735
|
+
callUint(executor2, getAddress3(token), allowanceData, "allowance")
|
|
736
|
+
]);
|
|
737
|
+
if (balanceAtomic < amountAtomic) throw new BridgeError(`Insufficient ${params.plan.sourceAsset.symbol} balance`);
|
|
738
|
+
return { routeId: params.plan.route.id, xReserveContract: route2.xReserveContract, tokenAddress: getAddress3(token), sourceChainId: route2.sourceChainId, remoteDomain: route2.remoteDomain, remoteRecipientBytes32, amountAtomic, maxFeeAtomic: route2.maxFeeAtomic, hookData, balanceAtomic, allowanceAtomic, approvalRequired: allowanceAtomic < amountAtomic };
|
|
739
|
+
}
|
|
740
|
+
function pendingReceipt(plan, status, id, approvalTxIds, quote, sourceTxId) {
|
|
741
|
+
return { id, protocol: "xreserve", status, ...sourceTxId ? { sourceTxId } : {}, protocolState: { routeId: plan.route.id, approvalTxIds, mintMode: plan.mintMode, intendedRecipient: plan.recipient, remoteRecipientBytes32: quote.remoteRecipientBytes32, hookData: quote.hookData, amountAtomic: quote.amountAtomic.toString(), maxFeeAtomic: quote.maxFeeAtomic.toString() } };
|
|
742
|
+
}
|
|
743
|
+
async function executeEvmXReserveTransfer(registry, executor2, params) {
|
|
744
|
+
const pollingIntervalMs = params.pollingIntervalMs ?? 1e3;
|
|
745
|
+
const confirmationTimeoutMs = params.confirmationTimeoutMs ?? 12e4;
|
|
746
|
+
if (!Number.isFinite(pollingIntervalMs) || pollingIntervalMs < 0 || !Number.isFinite(confirmationTimeoutMs) || confirmationTimeoutMs < 0) throw new BridgeError("Receipt polling controls must be non-negative finite numbers");
|
|
747
|
+
const route2 = metadata(registry, params.plan);
|
|
748
|
+
const quote = await quoteEvmXReserveTransfer(registry, executor2, params);
|
|
749
|
+
const owner = await account(executor2, params.plan);
|
|
750
|
+
const approvalTxIds = [];
|
|
751
|
+
if (quote.approvalRequired) {
|
|
752
|
+
const data2 = encodeFunctionData2({ abi: ERC20_ABI2, functionName: "approve", args: [route2.xReserveContract, quote.amountAtomic] });
|
|
753
|
+
const hash = await send(executor2, { from: owner, to: quote.tokenAddress, data: data2 });
|
|
754
|
+
approvalTxIds.push(hash);
|
|
755
|
+
const receipt2 = await wait(executor2, hash, confirmationTimeoutMs, pollingIntervalMs);
|
|
756
|
+
if (!receipt2) return { approvalTxIds, receipt: pendingReceipt(params.plan, "SOURCE_APPROVAL_PENDING", hash, approvalTxIds, quote) };
|
|
757
|
+
successful(receipt2, hash);
|
|
758
|
+
}
|
|
759
|
+
const data = encodeFunctionData2({ abi: XRESERVE_ABI, functionName: "depositToRemote", args: [quote.amountAtomic, route2.remoteDomain, quote.remoteRecipientBytes32, quote.tokenAddress, route2.maxFeeAtomic, quote.hookData] });
|
|
760
|
+
const sourceTxId = await send(executor2, { from: owner, to: route2.xReserveContract, data });
|
|
761
|
+
const receipt = await wait(executor2, sourceTxId, confirmationTimeoutMs, pollingIntervalMs);
|
|
762
|
+
if (!receipt) return { approvalTxIds, receipt: pendingReceipt(params.plan, "SOURCE_CONFIRMING", sourceTxId, approvalTxIds, quote, sourceTxId) };
|
|
763
|
+
successful(receipt, sourceTxId);
|
|
764
|
+
let matched;
|
|
765
|
+
for (const log of receipt.logs ?? []) {
|
|
766
|
+
if (log.address && getAddress3(log.address) !== route2.xReserveContract) continue;
|
|
767
|
+
try {
|
|
768
|
+
const decoded = decodeEventLog2({ abi: XRESERVE_ABI, data: log.data, topics: log.topics });
|
|
769
|
+
if (decoded.eventName === "DepositedToRemote") matched = { log, args: decoded.args };
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (!matched) throw new BridgeError("Confirmed receipt does not contain a valid DepositedToRemote event");
|
|
774
|
+
const { log: eventLog, args } = matched;
|
|
775
|
+
if (getAddress3(args.localToken) !== quote.tokenAddress || getAddress3(args.localDepositor) !== owner || args.value !== quote.amountAtomic || args.remoteDomain !== route2.remoteDomain || args.remoteRecipient.toLowerCase() !== quote.remoteRecipientBytes32.toLowerCase() || args.remoteToken.toLowerCase() !== route2.remoteTokenBytes32.toLowerCase() || args.maxFee !== route2.maxFeeAtomic || args.hookData.toLowerCase() !== quote.hookData.toLowerCase()) throw new BridgeError("DepositedToRemote event does not match the prepared transfer");
|
|
776
|
+
const rawIndex = eventLog.logIndex;
|
|
777
|
+
const logIndex = typeof rawIndex === "string" ? Number(BigInt(rawIndex)) : rawIndex;
|
|
778
|
+
if (!Number.isSafeInteger(logIndex) || logIndex == null || logIndex < 0) throw new BridgeError("DepositedToRemote log index is missing or invalid");
|
|
779
|
+
const nonce = calculateXReserveDepositNonce(route2.sourceDomain, sourceTxId, logIndex);
|
|
780
|
+
const payload = buildXReserveDepositPayload({ amount: args.value, remoteDomain: args.remoteDomain, remoteToken: args.remoteToken, remoteRecipient: args.remoteRecipient, localToken: args.localToken, depositor: args.localDepositor, maxFee: args.maxFee, nonce, hookData: args.hookData });
|
|
781
|
+
const messageHash = calculateXReserveMessageHash(payload);
|
|
782
|
+
return { approvalTxIds, receipt: { id: messageHash, protocol: "xreserve", status: "ATTESTATION_PENDING", sourceTxId, protocolState: { ...pendingReceipt(params.plan, "ATTESTATION_PENDING", messageHash, approvalTxIds, quote, sourceTxId).protocolState, sourceDomain: route2.sourceDomain, remoteDomain: route2.remoteDomain, depositLogIndex: logIndex, nonce, payload, messageHash, bridgeProgram: route2.bridgeProgram, wrapperProgram: route2.wrapperProgram } } };
|
|
783
|
+
}
|
|
784
|
+
async function getXReserveAttestation(registry, transport, params) {
|
|
785
|
+
const route2 = registry.routes.find((entry) => entry.id === params.routeId);
|
|
786
|
+
if (!route2) throw new BridgeError(`Unknown bridge route: ${params.routeId}`);
|
|
787
|
+
if (route2.protocol !== "xreserve" || route2.availability !== "active") throw new BridgeError(`xReserve route is not executable: ${params.routeId}`);
|
|
788
|
+
const attestationBaseUrl = route2.metadata?.attestationBaseUrl;
|
|
789
|
+
if (typeof attestationBaseUrl !== "string" || !attestationBaseUrl.startsWith("https://")) throw new BridgeError(`xReserve attestation URL is invalid: ${params.routeId}`);
|
|
790
|
+
const response = await transport(`${attestationBaseUrl}/${params.messageHash}`, params.signal ? { signal: params.signal } : void 0);
|
|
791
|
+
if (response.status === 404) return { status: "pending", messageHash: params.messageHash };
|
|
792
|
+
if (!response.ok) throw new BridgeError(`Circle attester request failed with HTTP ${response.status}`);
|
|
793
|
+
const body = await response.json();
|
|
794
|
+
const value = body.attestation;
|
|
795
|
+
if (!value || typeof value.payload !== "string" || !isHex2(value.payload) || typeof value.attestation !== "string" || !isHex2(value.attestation) || typeof value.messageHash !== "string" || !isHash2(value.messageHash) || value.messageHash.toLowerCase() !== params.messageHash.toLowerCase()) throw new BridgeError("Circle attester returned an invalid response");
|
|
796
|
+
if (calculateXReserveMessageHash(value.payload) !== params.messageHash) throw new BridgeError("Circle attestation payload does not match the requested message hash");
|
|
797
|
+
return { status: "complete", messageHash: params.messageHash, payload: value.payload, attestation: value.attestation };
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/actions/xreservePrivateMint.ts
|
|
801
|
+
import { isHash as isHash3, isHex as isHex3 } from "viem";
|
|
802
|
+
async function executeXReservePrivateMint(registry, executor2, params) {
|
|
803
|
+
const { plan, deposit, attestation } = params;
|
|
804
|
+
if (plan.protocol !== "xreserve" || plan.route.protocol !== "xreserve" || plan.mintMode !== "private") {
|
|
805
|
+
throw new BridgeError("private_mint requires a private xReserve transfer plan");
|
|
806
|
+
}
|
|
807
|
+
if (plan.registryVersion !== registry.version) throw new BridgeError(`Transfer plan uses registry ${plan.registryVersion}; expected ${registry.version}`);
|
|
808
|
+
const route2 = registry.routes.find((entry) => entry.id === plan.route.id);
|
|
809
|
+
if (!route2 || route2.protocol !== "xreserve" || route2.availability !== "active") throw new BridgeError(`xReserve route is not executable: ${plan.route.id}`);
|
|
810
|
+
const wrapperProgram = route2.metadata?.wrapperProgram;
|
|
811
|
+
if (typeof wrapperProgram !== "string" || !wrapperProgram.endsWith(".aleo")) throw new BridgeError(`xReserve wrapper program is invalid: ${plan.route.id}`);
|
|
812
|
+
if (deposit.protocol !== "xreserve" || deposit.status !== "ATTESTATION_PENDING") throw new BridgeError("Private mint requires a confirmed xReserve deposit awaiting attestation");
|
|
813
|
+
if (attestation.status !== "complete") throw new BridgeError("Private mint requires a completed Circle attestation");
|
|
814
|
+
const depositPayload = deposit.protocolState.payload;
|
|
815
|
+
const depositHash = deposit.protocolState.messageHash;
|
|
816
|
+
const intendedRecipient = deposit.protocolState.intendedRecipient;
|
|
817
|
+
const mintMode = deposit.protocolState.mintMode;
|
|
818
|
+
if (typeof depositPayload !== "string" || !isHex3(depositPayload, { strict: true })) throw new BridgeError("Deposit receipt is missing the canonical xReserve payload");
|
|
819
|
+
if (typeof depositHash !== "string" || !isHash3(depositHash)) throw new BridgeError("Deposit receipt is missing the Circle message hash");
|
|
820
|
+
if (typeof intendedRecipient !== "string" || intendedRecipient !== plan.recipient || mintMode !== "private") throw new BridgeError("Deposit receipt does not match the private mint plan");
|
|
821
|
+
if (attestation.payload.toLowerCase() !== depositPayload.toLowerCase() || attestation.messageHash.toLowerCase() !== depositHash.toLowerCase()) throw new BridgeError("Circle attestation does not match the confirmed deposit");
|
|
822
|
+
if (calculateXReserveMessageHash(attestation.payload) !== attestation.messageHash) throw new BridgeError("Circle attestation payload has an invalid message hash");
|
|
823
|
+
const secretNonce = plan.privateMintSecretNonce ?? "0scalar";
|
|
824
|
+
const expectedHookData = await buildXReserveHookData("private", plan.recipient, route2.environment, secretNonce);
|
|
825
|
+
const attestedHookData = `0x${attestation.payload.slice(-130)}`;
|
|
826
|
+
if (attestedHookData.toLowerCase() !== expectedHookData.toLowerCase()) {
|
|
827
|
+
throw new BridgeError("Private mint secret nonce and recipient do not match the attested hook data");
|
|
828
|
+
}
|
|
829
|
+
const result = await executor2.executeTransaction({
|
|
830
|
+
program: wrapperProgram,
|
|
831
|
+
function: "private_mint",
|
|
832
|
+
inputs: [
|
|
833
|
+
xReserveHexToAleoBytes(attestation.payload, 305),
|
|
834
|
+
xReserveHexToAleoBytes(attestation.attestation, 65),
|
|
835
|
+
xReserveHexToAleoBytes(attestation.messageHash, 32),
|
|
836
|
+
secretNonce,
|
|
837
|
+
plan.recipient
|
|
838
|
+
],
|
|
839
|
+
privateFee: params.privateFee ?? false
|
|
840
|
+
});
|
|
841
|
+
const transactionId = typeof result === "string" ? result : result.transactionId;
|
|
842
|
+
if (!transactionId) throw new BridgeError("Aleo wallet returned an empty private mint transaction id");
|
|
843
|
+
return {
|
|
844
|
+
transactionId,
|
|
845
|
+
receipt: {
|
|
846
|
+
...deposit,
|
|
847
|
+
status: "DESTINATION_CONFIRMING",
|
|
848
|
+
destinationTxId: transactionId,
|
|
849
|
+
protocolState: {
|
|
850
|
+
...deposit.protocolState,
|
|
851
|
+
attestation: attestation.attestation,
|
|
852
|
+
destinationProgram: wrapperProgram,
|
|
853
|
+
destinationFunction: "private_mint",
|
|
854
|
+
secretNonce
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/actions/xreserveBurn.ts
|
|
861
|
+
var ETHEREUM_DESTINATION_DOMAIN = 0;
|
|
862
|
+
function validatedRoute(registry, params) {
|
|
863
|
+
const { plan } = params;
|
|
864
|
+
if (plan.protocol !== "xreserve" || plan.route.protocol !== "xreserve") throw new BridgeError("USDCx burn requires an xReserve transfer plan");
|
|
865
|
+
if (plan.registryVersion !== registry.version) throw new BridgeError(`Transfer plan uses registry ${plan.registryVersion}; expected ${registry.version}`);
|
|
866
|
+
const route2 = registry.routes.find((entry) => entry.id === plan.route.id);
|
|
867
|
+
if (!route2 || route2.protocol !== "xreserve" || route2.availability !== "active") throw new BridgeError(`xReserve route is not executable: ${plan.route.id}`);
|
|
868
|
+
const sourceChain = registry.chains.find((chain) => chain.id === plan.sourceAsset.chainId);
|
|
869
|
+
const destinationChain = registry.chains.find((chain) => chain.id === plan.destinationAsset.chainId);
|
|
870
|
+
if (sourceChain?.family !== "aleo" || destinationChain?.family !== "evm") throw new BridgeError("USDCx burn action requires an Aleo-to-Ethereum route");
|
|
871
|
+
if (route2.sourceAssetId !== plan.sourceAsset.id || route2.destinationAssetId !== plan.destinationAsset.id) throw new BridgeError(`Transfer plan assets do not match configured route: ${route2.id}`);
|
|
872
|
+
const bridgeProgram = route2.metadata?.bridgeProgram;
|
|
873
|
+
const wrapperProgram = route2.metadata?.wrapperProgram;
|
|
874
|
+
const tokenProgram = route2.metadata?.remoteToken;
|
|
875
|
+
const nativeDomain = route2.metadata?.ethereumDestinationDomain;
|
|
876
|
+
if (typeof bridgeProgram !== "string" || !bridgeProgram.endsWith(".aleo")) throw new BridgeError(`xReserve bridge program is invalid: ${route2.id}`);
|
|
877
|
+
if (typeof wrapperProgram !== "string" || !wrapperProgram.endsWith(".aleo")) throw new BridgeError(`xReserve wrapper program is invalid: ${route2.id}`);
|
|
878
|
+
if (typeof tokenProgram !== "string" || !tokenProgram.endsWith(".aleo")) throw new BridgeError(`xReserve token program is invalid: ${route2.id}`);
|
|
879
|
+
if (nativeDomain !== ETHEREUM_DESTINATION_DOMAIN) throw new BridgeError(`xReserve Ethereum destination domain must be ${ETHEREUM_DESTINATION_DOMAIN}: ${route2.id}`);
|
|
880
|
+
return { route: route2, bridgeProgram, wrapperProgram, tokenProgram, nativeDomain };
|
|
881
|
+
}
|
|
882
|
+
function assertPrivateInputs(userRecord, merkleProof, tokenProgram) {
|
|
883
|
+
if (userRecord == null) throw new BridgeError("private_burn requires a USDCx userRecord input");
|
|
884
|
+
if (typeof userRecord === "object") {
|
|
885
|
+
if (userRecord.type !== "record" || userRecord.program !== tokenProgram || userRecord.recordname !== "Token") {
|
|
886
|
+
throw new BridgeError(`private_burn record requests must select ${tokenProgram}/Token`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (typeof merkleProof !== "string" || !merkleProof.startsWith("[") || !merkleProof.endsWith("]")) {
|
|
890
|
+
throw new BridgeError("private_burn requires an encoded [MerkleProof; 2] Aleo literal");
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function buildXReserveBurnCall(registry, params) {
|
|
894
|
+
const deployment = validatedRoute(registry, params);
|
|
895
|
+
const mode = params.mode ?? "private";
|
|
896
|
+
if (mode !== "public-as-signer" && mode !== "public" && mode !== "private") throw new BridgeError(`Unsupported USDCx burn mode: ${String(mode)}`);
|
|
897
|
+
const amountAtomic = parseDecimalAmount(params.plan.amountIn, params.plan.sourceAsset.decimals);
|
|
898
|
+
if (amountAtomic <= 0n) throw new BridgeError("USDCx burn amount must be greater than zero");
|
|
899
|
+
const nativeRecipientBytes32 = evmAddressToXReserveBytes32(params.plan.recipient);
|
|
900
|
+
const amount = `${amountAtomic}u128`;
|
|
901
|
+
const nativeDomain = `${deployment.nativeDomain}u32`;
|
|
902
|
+
const nativeRecipient = xReserveHexToAleoBytes(nativeRecipientBytes32, 32);
|
|
903
|
+
if (mode === "private") {
|
|
904
|
+
assertPrivateInputs(params.userRecord, params.merkleProof, deployment.tokenProgram);
|
|
905
|
+
return {
|
|
906
|
+
routeId: deployment.route.id,
|
|
907
|
+
mode,
|
|
908
|
+
program: deployment.wrapperProgram,
|
|
909
|
+
function: "private_burn",
|
|
910
|
+
inputs: [params.userRecord, amount, nativeDomain, nativeRecipient, params.merkleProof],
|
|
911
|
+
amountAtomic,
|
|
912
|
+
nativeDomain: deployment.nativeDomain,
|
|
913
|
+
nativeRecipientBytes32
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
routeId: deployment.route.id,
|
|
918
|
+
mode,
|
|
919
|
+
program: deployment.bridgeProgram,
|
|
920
|
+
function: mode === "public" ? "burn_public" : "burn_public_as_signer",
|
|
921
|
+
inputs: [amount, nativeDomain, nativeRecipient],
|
|
922
|
+
amountAtomic,
|
|
923
|
+
nativeDomain: deployment.nativeDomain,
|
|
924
|
+
nativeRecipientBytes32
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async function executeXReserveBurn(registry, executor2, params) {
|
|
928
|
+
const call = buildXReserveBurnCall(registry, params);
|
|
929
|
+
const result = await executor2.executeTransaction({
|
|
930
|
+
program: call.program,
|
|
931
|
+
function: call.function,
|
|
932
|
+
inputs: call.inputs,
|
|
933
|
+
privateFee: params.privateFee ?? false
|
|
934
|
+
});
|
|
935
|
+
const transactionId = typeof result === "string" ? result : result.transactionId;
|
|
936
|
+
if (!transactionId) throw new BridgeError("Aleo wallet returned an empty burn transaction id");
|
|
937
|
+
return {
|
|
938
|
+
transactionId,
|
|
939
|
+
receipt: {
|
|
940
|
+
id: transactionId,
|
|
941
|
+
protocol: "xreserve",
|
|
942
|
+
status: "SOURCE_CONFIRMING",
|
|
943
|
+
sourceTxId: transactionId,
|
|
944
|
+
protocolState: {
|
|
945
|
+
routeId: call.routeId,
|
|
946
|
+
burnMode: call.mode,
|
|
947
|
+
amountAtomic: call.amountAtomic.toString(),
|
|
948
|
+
nativeDomain: call.nativeDomain,
|
|
949
|
+
nativeRecipientBytes32: call.nativeRecipientBytes32,
|
|
950
|
+
sourceProgram: call.program,
|
|
951
|
+
sourceFunction: call.function,
|
|
952
|
+
forwardingService: "aleo-burn-attestation"
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/utils/hyperlane.ts
|
|
959
|
+
import bs58 from "bs58";
|
|
960
|
+
import { getAddress as getAddress4, hexToBytes as hexToBytes2, isAddress as isAddress4, padHex as padHex2 } from "viem";
|
|
961
|
+
function littleEndianU128(bytes) {
|
|
962
|
+
let value = 0n;
|
|
963
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
964
|
+
value |= BigInt(bytes[index]) << BigInt(index * 8);
|
|
965
|
+
}
|
|
966
|
+
return value;
|
|
967
|
+
}
|
|
968
|
+
function evmAddressToAleoHyperlaneRecipient(address) {
|
|
969
|
+
if (!isAddress4(address)) throw new BridgeError(`Invalid Ethereum Hyperlane recipient: ${address}`);
|
|
970
|
+
const recipient = hexToBytes2(padHex2(getAddress4(address), { size: 32 }));
|
|
971
|
+
return [
|
|
972
|
+
littleEndianU128(recipient.slice(0, 16)),
|
|
973
|
+
littleEndianU128(recipient.slice(16, 32))
|
|
974
|
+
];
|
|
975
|
+
}
|
|
976
|
+
function solanaAddressToAleoHyperlaneRecipient(address) {
|
|
977
|
+
try {
|
|
978
|
+
const recipient = bs58.decode(address);
|
|
979
|
+
if (recipient.length !== 32) throw new Error("invalid public key width");
|
|
980
|
+
return [
|
|
981
|
+
littleEndianU128(recipient.slice(0, 16)),
|
|
982
|
+
littleEndianU128(recipient.slice(16, 32))
|
|
983
|
+
];
|
|
984
|
+
} catch (cause) {
|
|
985
|
+
throw new BridgeError(`Invalid Solana Hyperlane recipient: ${address}`, { cause });
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/actions/aleoHyperlane.ts
|
|
990
|
+
var PLACEHOLDER_FIELDS = [
|
|
991
|
+
"aleoTokenType",
|
|
992
|
+
"aleoTokenOwner",
|
|
993
|
+
"aleoIsm",
|
|
994
|
+
"aleoHook",
|
|
995
|
+
"aleoTokenId",
|
|
996
|
+
"aleoMailboxDefaultHook",
|
|
997
|
+
"aleoMailboxRequiredHook",
|
|
998
|
+
"aleoRemoteRouterRecipient",
|
|
999
|
+
"aleoRemoteRouterGas",
|
|
1000
|
+
"aleoRecipient",
|
|
1001
|
+
"aleoAllowanceSpender0",
|
|
1002
|
+
"aleoAllowanceAmount0",
|
|
1003
|
+
"aleoAllowanceSpender1",
|
|
1004
|
+
"aleoAllowanceAmount1",
|
|
1005
|
+
"aleoAllowanceSpender2",
|
|
1006
|
+
"aleoAllowanceAmount2",
|
|
1007
|
+
"aleoAllowanceSpender3",
|
|
1008
|
+
"aleoAllowanceAmount3"
|
|
1009
|
+
];
|
|
1010
|
+
var APP_METADATA_FIELDS = /* @__PURE__ */ new Set([
|
|
1011
|
+
"aleoTokenType",
|
|
1012
|
+
"aleoTokenOwner",
|
|
1013
|
+
"aleoIsm",
|
|
1014
|
+
"aleoHook",
|
|
1015
|
+
"aleoTokenId"
|
|
1016
|
+
]);
|
|
1017
|
+
var MAILBOX_STATE_FIELDS = /* @__PURE__ */ new Set([
|
|
1018
|
+
"aleoMailboxDefaultHook",
|
|
1019
|
+
"aleoMailboxRequiredHook"
|
|
1020
|
+
]);
|
|
1021
|
+
var REMOTE_ROUTER_FIELDS = /* @__PURE__ */ new Set([
|
|
1022
|
+
"aleoRemoteRouterRecipient",
|
|
1023
|
+
"aleoRemoteRouterGas"
|
|
1024
|
+
]);
|
|
1025
|
+
var ALLOWANCE_SPENDER_FIELDS = /* @__PURE__ */ new Set([
|
|
1026
|
+
"aleoAllowanceSpender0",
|
|
1027
|
+
"aleoAllowanceSpender1",
|
|
1028
|
+
"aleoAllowanceSpender2",
|
|
1029
|
+
"aleoAllowanceSpender3"
|
|
1030
|
+
]);
|
|
1031
|
+
var UNUSED_ALLOWANCE_AMOUNT_FIELDS = /* @__PURE__ */ new Set([
|
|
1032
|
+
"aleoAllowanceAmount1",
|
|
1033
|
+
"aleoAllowanceAmount2",
|
|
1034
|
+
"aleoAllowanceAmount3"
|
|
1035
|
+
]);
|
|
1036
|
+
function metadataString(route2, key) {
|
|
1037
|
+
const value = route2.metadata?.[key];
|
|
1038
|
+
if (typeof value !== "string" || value.length === 0) throw new BridgeError(`Hyperlane route metadata ${key} is missing: ${route2.id}`);
|
|
1039
|
+
return value;
|
|
1040
|
+
}
|
|
1041
|
+
function metadataNumber(route2, key) {
|
|
1042
|
+
const value = route2.metadata?.[key];
|
|
1043
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new BridgeError(`Hyperlane route metadata ${key} is invalid: ${route2.id}`);
|
|
1044
|
+
return value;
|
|
1045
|
+
}
|
|
1046
|
+
function optionalMetadataNumber(route2, key, fallback) {
|
|
1047
|
+
return route2.metadata?.[key] == null ? fallback : metadataNumber(route2, key);
|
|
1048
|
+
}
|
|
1049
|
+
function validatedRoute2(registry, params) {
|
|
1050
|
+
const { plan } = params;
|
|
1051
|
+
if (plan.protocol !== "hyperlane" || plan.route.protocol !== "hyperlane") throw new BridgeError("Aleo transfer_remote requires a Hyperlane transfer plan");
|
|
1052
|
+
if (plan.registryVersion !== registry.version) throw new BridgeError(`Transfer plan uses registry ${plan.registryVersion}; expected ${registry.version}`);
|
|
1053
|
+
const route2 = registry.routes.find((entry) => entry.id === plan.route.id);
|
|
1054
|
+
if (!route2 || route2.protocol !== "hyperlane") throw new BridgeError(`Hyperlane route is not configured: ${plan.route.id}`);
|
|
1055
|
+
if (route2.sourceAssetId !== plan.sourceAsset.id || route2.destinationAssetId !== plan.destinationAsset.id) throw new BridgeError(`Transfer plan assets do not match configured route: ${route2.id}`);
|
|
1056
|
+
const sourceChain = registry.chains.find((chain) => chain.id === plan.sourceAsset.chainId);
|
|
1057
|
+
if (sourceChain?.family !== "aleo") throw new BridgeError("transfer_remote requires an Aleo source asset");
|
|
1058
|
+
const destinationChain = registry.chains.find((chain) => chain.id === plan.destinationAsset.chainId);
|
|
1059
|
+
if (!destinationChain) throw new BridgeError(`Destination chain is not configured: ${plan.destinationAsset.chainId}`);
|
|
1060
|
+
const program = metadataString(route2, "aleoRouterProgram");
|
|
1061
|
+
if (!program.endsWith(".aleo")) throw new BridgeError(`Aleo Warp Route program is invalid: ${route2.id}`);
|
|
1062
|
+
return { route: route2, program, destinationChain };
|
|
1063
|
+
}
|
|
1064
|
+
function allowance(route2, index) {
|
|
1065
|
+
return `{ spender: ${metadataString(route2, `aleoAllowanceSpender${index}`)}, amount: ${metadataString(route2, `aleoAllowanceAmount${index}`)}u64 }`;
|
|
1066
|
+
}
|
|
1067
|
+
function buildAleoHyperlaneTransferRemoteCall(registry, params) {
|
|
1068
|
+
if (params.mode != null && params.mode !== "caller" && params.mode !== "signer") {
|
|
1069
|
+
throw new BridgeError(`Unsupported Aleo Hyperlane transfer mode: ${String(params.mode)}`);
|
|
1070
|
+
}
|
|
1071
|
+
const { route: route2, program, destinationChain } = validatedRoute2(registry, params);
|
|
1072
|
+
const amountAtomic = parseDecimalAmount(params.plan.amountIn, params.plan.sourceAsset.decimals);
|
|
1073
|
+
const destination = metadataNumber(route2, "aleoDestinationDomain");
|
|
1074
|
+
const localDecimals = optionalMetadataNumber(route2, "aleoLocalDecimals", params.plan.sourceAsset.decimals);
|
|
1075
|
+
const remoteDecimals = optionalMetadataNumber(route2, "aleoRemoteDecimals", params.plan.destinationAsset.decimals);
|
|
1076
|
+
const appMetadata = `{ token_type: ${metadataString(route2, "aleoTokenType")}u8, token_owner: ${metadataString(route2, "aleoTokenOwner")}, ism: ${metadataString(route2, "aleoIsm")}, hook: ${metadataString(route2, "aleoHook")}, token_id: ${metadataString(route2, "aleoTokenId")}, local_decimals: ${localDecimals}u8, remote_decimals: ${remoteDecimals}u8 }`;
|
|
1077
|
+
const mailboxState = `{ default_hook: ${metadataString(route2, "aleoMailboxDefaultHook")}, required_hook: ${metadataString(route2, "aleoMailboxRequiredHook")} }`;
|
|
1078
|
+
const remoteRouter = `{ domain: ${destination}u32, recipient: ${metadataString(route2, "aleoRemoteRouterRecipient")}, gas: ${metadataString(route2, "aleoRemoteRouterGas")}u128 }`;
|
|
1079
|
+
const recipientLimbs = destinationChain.family === "evm" ? evmAddressToAleoHyperlaneRecipient(params.plan.recipient) : destinationChain.family === "solana" ? solanaAddressToAleoHyperlaneRecipient(params.plan.recipient) : void 0;
|
|
1080
|
+
const recipient = recipientLimbs ? `[${recipientLimbs[0]}u128, ${recipientLimbs[1]}u128]` : metadataString(route2, "aleoRecipient");
|
|
1081
|
+
const allowances = `[${[0, 1, 2, 3].map((index) => allowance(route2, index)).join(", ")}]`;
|
|
1082
|
+
const usesPlaceholderConfiguration = route2.metadata?.aleoPlaceholderConfiguration === true;
|
|
1083
|
+
let placeholderFields = route2.metadata?.aleoAppMetadataVerified === true ? PLACEHOLDER_FIELDS.filter((field) => !APP_METADATA_FIELDS.has(field)) : PLACEHOLDER_FIELDS;
|
|
1084
|
+
if (route2.metadata?.aleoMailboxStateVerified === true) {
|
|
1085
|
+
placeholderFields = placeholderFields.filter((field) => !MAILBOX_STATE_FIELDS.has(field));
|
|
1086
|
+
}
|
|
1087
|
+
if (route2.metadata?.aleoRemoteRouterVerified === true) {
|
|
1088
|
+
placeholderFields = placeholderFields.filter((field) => !REMOTE_ROUTER_FIELDS.has(field));
|
|
1089
|
+
}
|
|
1090
|
+
if (route2.metadata?.aleoAllowanceSpendersVerified === true) {
|
|
1091
|
+
placeholderFields = placeholderFields.filter((field) => !ALLOWANCE_SPENDER_FIELDS.has(field));
|
|
1092
|
+
}
|
|
1093
|
+
if (route2.metadata?.aleoUnusedAllowancesVerified === true) {
|
|
1094
|
+
placeholderFields = placeholderFields.filter((field) => !UNUSED_ALLOWANCE_AMOUNT_FIELDS.has(field));
|
|
1095
|
+
}
|
|
1096
|
+
if (recipientLimbs) {
|
|
1097
|
+
placeholderFields = placeholderFields.filter((field) => field !== "aleoRecipient");
|
|
1098
|
+
}
|
|
1099
|
+
const functionName = params.mode === "signer" ? "transfer_remote_as_signer" : "transfer_remote";
|
|
1100
|
+
return {
|
|
1101
|
+
routeId: route2.id,
|
|
1102
|
+
program,
|
|
1103
|
+
function: functionName,
|
|
1104
|
+
inputs: [
|
|
1105
|
+
appMetadata,
|
|
1106
|
+
mailboxState,
|
|
1107
|
+
remoteRouter,
|
|
1108
|
+
`${destination}u32`,
|
|
1109
|
+
recipient,
|
|
1110
|
+
`${amountAtomic}u128`,
|
|
1111
|
+
allowances
|
|
1112
|
+
],
|
|
1113
|
+
amountAtomic,
|
|
1114
|
+
usesPlaceholderConfiguration,
|
|
1115
|
+
placeholderFields: usesPlaceholderConfiguration ? placeholderFields : []
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
async function executeAleoHyperlaneTransferRemote(registry, executor2, params) {
|
|
1119
|
+
const call = buildAleoHyperlaneTransferRemoteCall(registry, params);
|
|
1120
|
+
if (call.usesPlaceholderConfiguration) {
|
|
1121
|
+
throw new BridgeError(`Aleo Hyperlane route contains non-executable placeholder configuration: ${call.routeId}`);
|
|
1122
|
+
}
|
|
1123
|
+
const route2 = registry.routes.find((entry) => entry.id === call.routeId);
|
|
1124
|
+
if (route2?.availability !== "active") {
|
|
1125
|
+
throw new BridgeError(`Aleo Hyperlane route is not active: ${call.routeId}`);
|
|
1126
|
+
}
|
|
1127
|
+
const result = await executor2.executeTransaction({
|
|
1128
|
+
program: call.program,
|
|
1129
|
+
function: call.function,
|
|
1130
|
+
inputs: call.inputs,
|
|
1131
|
+
privateFee: params.privateFee ?? false
|
|
1132
|
+
});
|
|
1133
|
+
const transactionId = typeof result === "string" ? result : result.transactionId;
|
|
1134
|
+
if (!transactionId) throw new BridgeError("Aleo wallet returned an empty Hyperlane transaction id");
|
|
1135
|
+
return {
|
|
1136
|
+
transactionId,
|
|
1137
|
+
receipt: {
|
|
1138
|
+
id: transactionId,
|
|
1139
|
+
protocol: "hyperlane",
|
|
1140
|
+
status: "SOURCE_CONFIRMING",
|
|
1141
|
+
sourceTxId: transactionId,
|
|
1142
|
+
protocolState: { routeId: call.routeId, sourceProgram: call.program, sourceFunction: call.function }
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// src/clients/decorators/bridge.ts
|
|
1148
|
+
function bridgeActions(_client, config) {
|
|
1149
|
+
const evmExecutor = () => {
|
|
1150
|
+
if (!config.executors?.evm) {
|
|
1151
|
+
throw new BridgeError("An EVM executor is required for Ethereum bridge actions");
|
|
1152
|
+
}
|
|
1153
|
+
return config.executors.evm;
|
|
1154
|
+
};
|
|
1155
|
+
const xReserveTransport = () => {
|
|
1156
|
+
if (!config.xReserveHttpTransport) throw new BridgeError("An xReserve HTTP transport is required for attestation requests");
|
|
1157
|
+
return config.xReserveHttpTransport;
|
|
1158
|
+
};
|
|
1159
|
+
const aleoExecutor = () => {
|
|
1160
|
+
if (!config.executors?.aleo) throw new BridgeError("An Aleo executor is required for Aleo bridge transactions");
|
|
1161
|
+
return config.executors.aleo;
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
getAssets: (params = {}) => getProtocolAssets(config.registry, {
|
|
1165
|
+
...params,
|
|
1166
|
+
environment: params.environment ?? config.environment
|
|
1167
|
+
}),
|
|
1168
|
+
getRoutes: (params = {}) => getProtocolRoutes(config.registry, {
|
|
1169
|
+
...params,
|
|
1170
|
+
environment: params.environment ?? config.environment
|
|
1171
|
+
}),
|
|
1172
|
+
prepareTransfer: (params) => prepareTransfer(config.registry, params),
|
|
1173
|
+
quoteEvmHyperlaneTransfer: async (params) => quoteEvmHyperlaneTransfer(config.registry, evmExecutor(), params),
|
|
1174
|
+
executeEvmHyperlaneTransfer: async (params) => executeEvmHyperlaneTransfer(config.registry, evmExecutor(), params),
|
|
1175
|
+
quoteEvmXReserveTransfer: async (params) => quoteEvmXReserveTransfer(config.registry, evmExecutor(), params),
|
|
1176
|
+
executeEvmXReserveTransfer: async (params) => executeEvmXReserveTransfer(config.registry, evmExecutor(), params),
|
|
1177
|
+
getXReserveAttestation: async (params) => getXReserveAttestation(config.registry, xReserveTransport(), params),
|
|
1178
|
+
executeXReservePrivateMint: async (params) => executeXReservePrivateMint(config.registry, aleoExecutor(), params),
|
|
1179
|
+
executeXReserveBurn: async (params) => executeXReserveBurn(config.registry, aleoExecutor(), params),
|
|
1180
|
+
buildAleoHyperlaneTransferRemoteCall: (params) => buildAleoHyperlaneTransferRemoteCall(config.registry, params),
|
|
1181
|
+
executeAleoHyperlaneTransferRemote: async (params) => executeAleoHyperlaneTransferRemote(config.registry, aleoExecutor(), params)
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/registry/default.ts
|
|
1186
|
+
var EVM_ADDRESS = "^0x[0-9a-fA-F]{40}$";
|
|
1187
|
+
var SOLANA_ADDRESS = "^[1-9A-HJ-NP-Za-km-z]{32,44}$";
|
|
1188
|
+
var ALEO_ADDRESS = "^aleo1[0-9a-z]{58}$";
|
|
1189
|
+
var chains = [
|
|
1190
|
+
{ id: "aleo", displayName: "Aleo", family: "aleo", environment: "mainnet", nativeCurrencySymbol: "ALEO", protocolDomains: { xreserve: 10002, hyperlane: 1634493807 } },
|
|
1191
|
+
{ id: "ethereum", displayName: "Ethereum", family: "evm", environment: "mainnet", nativeCurrencySymbol: "ETH", protocolDomains: { xreserve: 0, hyperlane: 1 } },
|
|
1192
|
+
{ id: "solana", displayName: "Solana", family: "solana", environment: "mainnet", nativeCurrencySymbol: "SOL", protocolDomains: { hyperlane: 1399811149 } },
|
|
1193
|
+
{ id: "base", displayName: "Base", family: "evm", environment: "mainnet", nativeCurrencySymbol: "ETH" },
|
|
1194
|
+
{ id: "hyperevm", displayName: "HyperEVM", family: "evm", environment: "mainnet", nativeCurrencySymbol: "HYPE" },
|
|
1195
|
+
{ id: "aleo-testnet", displayName: "Aleo Testnet", family: "aleo", environment: "testnet", nativeCurrencySymbol: "ALEO", protocolDomains: { xreserve: 10002, hyperlane: 1617853565 } },
|
|
1196
|
+
{ id: "sepolia", displayName: "Ethereum Sepolia", family: "evm", environment: "testnet", nativeCurrencySymbol: "ETH", protocolDomains: { hyperlane: 11155111 } }
|
|
1197
|
+
];
|
|
1198
|
+
var assets = [
|
|
1199
|
+
{ id: "aleo/aleo", chainId: "aleo", symbol: "ALEO", name: "Aleo", decimals: 6, kind: "native", locator: { kind: "aleo-program", value: "credits.aleo" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1200
|
+
{ id: "aleo/usdcx", chainId: "aleo", symbol: "USDCx", name: "USDCx", decimals: 6, kind: "token", locator: { kind: "aleo-program", value: "usdcx_stablecoin.aleo" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1201
|
+
{ id: "aleo/eth", chainId: "aleo", symbol: "ETH", name: "Hyperlane ETH", decimals: 18, kind: "token", locator: { kind: "aleo-program", value: "hyp_warp_token_eth_v2.aleo", tokenId: "aleo1t7f29tq9qng2lfvrkpcuvu59jn24hrmzqdyqfn6p0u5p80npfvqqecmkj8" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1202
|
+
{ id: "aleo/wbtc", chainId: "aleo", symbol: "WBTC", name: "Hyperlane WBTC", decimals: 8, kind: "token", locator: { kind: "aleo-program", value: "hyp_warp_token_wbtc_v2.aleo", tokenId: "aleo1240fsvz2dhmj0cdtt8mc0yc8um9fmu236rqcl2qnlj9703hd2vpsdwyrtf" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1203
|
+
{ id: "aleo/usdt", chainId: "aleo", symbol: "USDT", name: "Hyperlane USDT", decimals: 6, kind: "token", locator: { kind: "aleo-program", value: "hyp_warp_token_usdt_v2.aleo", tokenId: "aleo18yynfz0lrfx0tund540vy2z7gju7ekgqsueg5jgu28mpm2z42ufq7qua8y" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1204
|
+
{ id: "aleo/sol", chainId: "aleo", symbol: "SOL", name: "Hyperlane SOL", decimals: 9, kind: "token", locator: { kind: "aleo-program", value: "hyp_warp_token_sol_v2.aleo", tokenId: "aleo1aa0zt0vg9uwknekpqeefkvad55swp7833wc5crp2prv0lm4djuxs5r7k6v" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1205
|
+
{ id: "aleo/usad", chainId: "aleo", symbol: "USAD", name: "USAD", decimals: 6, kind: "token", locator: { kind: "aleo-program", value: "usad_stablecoin.aleo" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1206
|
+
{ id: "ethereum/usdc", chainId: "ethereum", symbol: "USDC", name: "USD Coin", decimals: 6, kind: "token", locator: { kind: "evm-contract", value: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, addressValidationRegex: EVM_ADDRESS },
|
|
1207
|
+
{ id: "ethereum/eth", chainId: "ethereum", symbol: "ETH", name: "Ether", decimals: 18, kind: "native", locator: { kind: "native", value: "ETH" }, addressValidationRegex: EVM_ADDRESS },
|
|
1208
|
+
{ id: "ethereum/wbtc", chainId: "ethereum", symbol: "WBTC", name: "Wrapped Bitcoin", decimals: 8, kind: "token", locator: { kind: "evm-contract", value: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" }, addressValidationRegex: EVM_ADDRESS },
|
|
1209
|
+
{ id: "ethereum/usdt", chainId: "ethereum", symbol: "USDT", name: "Tether USD", decimals: 6, kind: "token", locator: { kind: "evm-contract", value: "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, addressValidationRegex: EVM_ADDRESS },
|
|
1210
|
+
{ id: "ethereum/aleo", chainId: "ethereum", symbol: "ALEO", name: "Hyperlane ALEO", decimals: 6, kind: "token", addressValidationRegex: EVM_ADDRESS },
|
|
1211
|
+
{ id: "ethereum/usad", chainId: "ethereum", symbol: "USAD", name: "USAD route collateral", decimals: 6, kind: "token", addressValidationRegex: EVM_ADDRESS },
|
|
1212
|
+
{ id: "solana/sol", chainId: "solana", symbol: "SOL", name: "Solana", decimals: 9, kind: "native", locator: { kind: "native", value: "SOL" }, addressValidationRegex: SOLANA_ADDRESS },
|
|
1213
|
+
{ id: "solana/aleo", chainId: "solana", symbol: "ALEO", name: "Hyperlane ALEO", decimals: 6, kind: "token", addressValidationRegex: SOLANA_ADDRESS },
|
|
1214
|
+
{ id: "base/aleo", chainId: "base", symbol: "ALEO", name: "Hyperlane ALEO", decimals: 6, kind: "token", addressValidationRegex: EVM_ADDRESS },
|
|
1215
|
+
{ id: "hyperevm/aleo", chainId: "hyperevm", symbol: "ALEO", name: "Hyperlane ALEO", decimals: 6, kind: "token", addressValidationRegex: EVM_ADDRESS },
|
|
1216
|
+
{ id: "aleo-testnet/usdcx", chainId: "aleo-testnet", symbol: "USDCx", name: "Testnet USDCx", decimals: 6, kind: "token", locator: { kind: "aleo-program", value: "test_usdcx_stablecoin.aleo" }, addressValidationRegex: ALEO_ADDRESS },
|
|
1217
|
+
{ id: "sepolia/usdc", chainId: "sepolia", symbol: "USDC", name: "Testnet USD Coin", decimals: 6, kind: "token", locator: { kind: "evm-contract", value: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" }, addressValidationRegex: EVM_ADDRESS }
|
|
1218
|
+
];
|
|
1219
|
+
var XRESERVE_SOURCE = "https://developers.circle.com/xreserve/references/supported-blockchains-and-domains";
|
|
1220
|
+
var HYPERLANE_REGISTRY_COMMIT = "2621c16f2db1ccb46643265c110dac5ca2c7c51a";
|
|
1221
|
+
var HYPERLANE_SOURCE = `https://github.com/hyperlane-xyz/hyperlane-registry/tree/${HYPERLANE_REGISTRY_COMMIT}/deployments/warp_routes`;
|
|
1222
|
+
var ALEO_ETH_PROGRAM_SOURCE = "https://explorer.provable.com/program/hyp_warp_token_eth_v2.aleo";
|
|
1223
|
+
var ALEO_ETH_APP_METADATA_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_eth_v2.aleo/mapping/app_metadata/true";
|
|
1224
|
+
var ALEO_ETH_REMOTE_ROUTER_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_eth_v2.aleo/mapping/remote_routers/1u32";
|
|
1225
|
+
var ALEO_ETH_SAMPLE_TRANSFER_SOURCE = "https://explorer.provable.com/transaction/at1vu0yckkms887zkl3qz7plnncd56jtf5zeal4uj2808upsjkusy8q7yp9v8";
|
|
1226
|
+
var ALEO_WBTC_PROGRAM_SOURCE = "https://explorer.provable.com/program/hyp_warp_token_wbtc_v2.aleo";
|
|
1227
|
+
var ALEO_WBTC_APP_METADATA_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_wbtc_v2.aleo/mapping/app_metadata/true";
|
|
1228
|
+
var ALEO_WBTC_REMOTE_ROUTER_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_wbtc_v2.aleo/mapping/remote_routers/1u32";
|
|
1229
|
+
var ALEO_USDT_PROGRAM_SOURCE = "https://explorer.provable.com/program/hyp_warp_token_usdt_v2.aleo";
|
|
1230
|
+
var ALEO_USDT_APP_METADATA_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_usdt_v2.aleo/mapping/app_metadata/true";
|
|
1231
|
+
var ALEO_USDT_ETHEREUM_REMOTE_ROUTER_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_usdt_v2.aleo/mapping/remote_routers/1u32";
|
|
1232
|
+
var ALEO_USDT_SAMPLE_TRANSFER_SOURCE = "https://explorer.provable.com/transaction/at19caeeee8v3xc4kfwen4tx89f0tnggrpjp0anrhq2ca3y82xr9q8qyz8a9r";
|
|
1233
|
+
var ALEO_USDT_HYPERLANE_CONFIG_SOURCE = "https://github.com/hyperlane-xyz/hyperlane-registry/blob/418056e21734d26a7d14692e0ec5e902cc9e86bf/deployments/warp_routes/USDT/aleo-config.yaml";
|
|
1234
|
+
var ALEO_SOL_PROGRAM_SOURCE = "https://explorer.provable.com/program/hyp_warp_token_sol_v2.aleo";
|
|
1235
|
+
var ALEO_SOL_APP_METADATA_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_sol_v2.aleo/mapping/app_metadata/true";
|
|
1236
|
+
var ALEO_SOL_REMOTE_ROUTER_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_warp_token_sol_v2.aleo/mapping/remote_routers/1399811149u32";
|
|
1237
|
+
var ALEO_SOL_HYPERLANE_CONFIG_SOURCE = "https://github.com/hyperlane-xyz/hyperlane-registry/blob/418056e21734d26a7d14692e0ec5e902cc9e86bf/deployments/warp_routes/SOL/aleo-config.yaml";
|
|
1238
|
+
var ALEO_MAILBOX_PROGRAM_SOURCE = "https://explorer.provable.com/program/hyp_mailbox.aleo";
|
|
1239
|
+
var ALEO_MAILBOX_METADATA_SOURCE = "https://api.explorer.provable.com/v2/mainnet/program/hyp_mailbox.aleo/mapping/mailbox/true";
|
|
1240
|
+
var ETHEREUM_HYPERLANE_COMMON = {
|
|
1241
|
+
sourceChainId: 1,
|
|
1242
|
+
destinationDomain: 1634493807,
|
|
1243
|
+
mailboxAddress: "0xc005dc82818d67AF737725bD4bf75435d065D239",
|
|
1244
|
+
interchainGasPaymaster: "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611",
|
|
1245
|
+
interchainSecurityModule: "0x0000000000000000000000000000000000000000",
|
|
1246
|
+
registryCommit: HYPERLANE_REGISTRY_COMMIT
|
|
1247
|
+
};
|
|
1248
|
+
var ETH_HYPERLANE_METADATA = {
|
|
1249
|
+
...ETHEREUM_HYPERLANE_COMMON,
|
|
1250
|
+
routerAddress: "0x38D447694f5c1f773ae3132cf93bF30B7Ec1Fa5A",
|
|
1251
|
+
routerType: "native",
|
|
1252
|
+
destinationRouter: "hyp_warp_token_eth_v2.aleo/aleo1t7f29tq9qng2lfvrkpcuvu59jn24hrmzqdyqfn6p0u5p80npfvqqecmkj8"
|
|
1253
|
+
};
|
|
1254
|
+
var WBTC_HYPERLANE_METADATA = {
|
|
1255
|
+
...ETHEREUM_HYPERLANE_COMMON,
|
|
1256
|
+
routerAddress: "0x20CDC85778b732073F7EecEF3DF25c0d310f8772",
|
|
1257
|
+
routerType: "collateral",
|
|
1258
|
+
tokenAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
1259
|
+
destinationRouter: "hyp_warp_token_wbtc_v2.aleo/aleo1240fsvz2dhmj0cdtt8mc0yc8um9fmu236rqcl2qnlj9703hd2vpsdwyrtf"
|
|
1260
|
+
};
|
|
1261
|
+
var USDT_HYPERLANE_METADATA = {
|
|
1262
|
+
...ETHEREUM_HYPERLANE_COMMON,
|
|
1263
|
+
routerAddress: "0x3C2064D78e4578E8F936E3db42aEF044E33FBF31",
|
|
1264
|
+
routerType: "collateral",
|
|
1265
|
+
tokenAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
1266
|
+
destinationRouter: "hyp_warp_token_usdt_v2.aleo/aleo18yynfz0lrfx0tund540vy2z7gju7ekgqsueg5jgu28mpm2z42ufq7qua8y",
|
|
1267
|
+
requiresApprovalReset: true
|
|
1268
|
+
};
|
|
1269
|
+
var ALEO_PLACEHOLDER_ADDRESS = "aleo1kypwp5m7qtk9mwazgcpg0tq8aal23mnrvwfvug65qgcg9xvsrqgspyjm6n";
|
|
1270
|
+
var ALEO_PLACEHOLDER_BYTES32 = `[${Array.from({ length: 32 }, () => "0u8").join(", ")}]`;
|
|
1271
|
+
var ALEO_MAILBOX_METADATA = {
|
|
1272
|
+
aleoMailboxStateVerified: true,
|
|
1273
|
+
aleoMailboxProgram: "hyp_mailbox.aleo",
|
|
1274
|
+
aleoMailboxProgramEdition: 0,
|
|
1275
|
+
aleoMailboxProgramSource: ALEO_MAILBOX_PROGRAM_SOURCE,
|
|
1276
|
+
aleoMailboxMetadataSource: ALEO_MAILBOX_METADATA_SOURCE,
|
|
1277
|
+
aleoMailboxMetadataReviewedAt: "2026-08-17",
|
|
1278
|
+
aleoMailboxLocalDomain: 1634493807,
|
|
1279
|
+
aleoMailboxObservedNonce: 170,
|
|
1280
|
+
aleoMailboxObservedProcessCount: 291,
|
|
1281
|
+
aleoMailboxDefaultIsm: "aleo1yvf5kcsdgnescqq2lar83mms79yh3ugvc3y0mdnlgvx4lyh5zugqr9hptk",
|
|
1282
|
+
aleoMailboxDefaultHook: "aleo194tz0jmyq8rd9htvnqppqw4jqerk2p2zd8plzn3sxl06wcgsm5pq9fka74",
|
|
1283
|
+
aleoMailboxRequiredHook: "aleo1yxevh9qgxehej46j7vueplwjcpfdfml2dje3ey4ukzknx7wzasgqnxgq82",
|
|
1284
|
+
aleoMailboxDispatchProxy: "aleo1sge9kmjzs3d8fqrscy4hwn7vf9vw4jcxe877lv0m2w8hay78lsxsqg975s",
|
|
1285
|
+
aleoMailboxOwner: "aleo1ypf8xgvz560ukw25hufj3d77gx69pdcy70nssdfdxd97j80d7cqs98d7x8"
|
|
1286
|
+
};
|
|
1287
|
+
function aleoHyperlanePlaceholders(program, destinationDomain) {
|
|
1288
|
+
return {
|
|
1289
|
+
aleoRouterProgram: program,
|
|
1290
|
+
aleoDestinationDomain: destinationDomain,
|
|
1291
|
+
aleoPlaceholderConfiguration: true,
|
|
1292
|
+
aleoTokenType: "0",
|
|
1293
|
+
aleoTokenOwner: ALEO_PLACEHOLDER_ADDRESS,
|
|
1294
|
+
aleoIsm: ALEO_PLACEHOLDER_ADDRESS,
|
|
1295
|
+
aleoHook: ALEO_PLACEHOLDER_ADDRESS,
|
|
1296
|
+
aleoTokenId: "0field",
|
|
1297
|
+
aleoRemoteRouterRecipient: ALEO_PLACEHOLDER_BYTES32,
|
|
1298
|
+
aleoRemoteRouterGas: "0",
|
|
1299
|
+
aleoRecipient: "[0u128, 0u128]",
|
|
1300
|
+
aleoAllowanceSpender0: ALEO_PLACEHOLDER_ADDRESS,
|
|
1301
|
+
aleoAllowanceAmount0: "0",
|
|
1302
|
+
aleoAllowanceSpender1: ALEO_PLACEHOLDER_ADDRESS,
|
|
1303
|
+
aleoAllowanceAmount1: "0",
|
|
1304
|
+
aleoAllowanceSpender2: ALEO_PLACEHOLDER_ADDRESS,
|
|
1305
|
+
aleoAllowanceAmount2: "0",
|
|
1306
|
+
aleoAllowanceSpender3: ALEO_PLACEHOLDER_ADDRESS,
|
|
1307
|
+
aleoAllowanceAmount3: "0",
|
|
1308
|
+
...ALEO_MAILBOX_METADATA
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
var ALEO_WBTC_APP_METADATA = {
|
|
1312
|
+
aleoAppMetadataVerified: true,
|
|
1313
|
+
aleoProgramSource: ALEO_WBTC_PROGRAM_SOURCE,
|
|
1314
|
+
aleoAppMetadataSource: ALEO_WBTC_APP_METADATA_SOURCE,
|
|
1315
|
+
aleoAppMetadataReviewedAt: "2026-08-17",
|
|
1316
|
+
aleoProgramEdition: 0,
|
|
1317
|
+
aleoTokenType: "1",
|
|
1318
|
+
aleoTokenOwner: "aleo14jauje2a5sncm9u5t3mt6qqv3eq2hatkddskccs0dvsy35a0x58q0d6f95",
|
|
1319
|
+
aleoIsm: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1320
|
+
aleoHook: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1321
|
+
aleoTokenId: "1505227928464760254508513036497943623956572091841806589002910775534260084309field",
|
|
1322
|
+
aleoLocalDecimals: 8,
|
|
1323
|
+
aleoRemoteDecimals: 8
|
|
1324
|
+
};
|
|
1325
|
+
var ALEO_ETH_APP_METADATA = {
|
|
1326
|
+
aleoAppMetadataVerified: true,
|
|
1327
|
+
aleoProgramSource: ALEO_ETH_PROGRAM_SOURCE,
|
|
1328
|
+
aleoAppMetadataSource: ALEO_ETH_APP_METADATA_SOURCE,
|
|
1329
|
+
aleoAppMetadataReviewedAt: "2026-08-17",
|
|
1330
|
+
aleoProgramEdition: 0,
|
|
1331
|
+
aleoTokenType: "1",
|
|
1332
|
+
aleoTokenOwner: "aleo1wq6f6qdqya44avznygz5hae40u3mjg64w0r93a4qfu4utpf8cg9q566f4r",
|
|
1333
|
+
aleoIsm: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1334
|
+
aleoHook: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1335
|
+
aleoTokenId: "133188123661477349522757068766864658505569365361420630212878794317749195359field",
|
|
1336
|
+
aleoLocalDecimals: 18,
|
|
1337
|
+
aleoRemoteDecimals: 18
|
|
1338
|
+
};
|
|
1339
|
+
var ALEO_USDT_APP_METADATA = {
|
|
1340
|
+
aleoAppMetadataVerified: true,
|
|
1341
|
+
aleoProgramSource: ALEO_USDT_PROGRAM_SOURCE,
|
|
1342
|
+
aleoAppMetadataSource: ALEO_USDT_APP_METADATA_SOURCE,
|
|
1343
|
+
aleoAppMetadataReviewedAt: "2026-08-17",
|
|
1344
|
+
aleoProgramEdition: 1,
|
|
1345
|
+
aleoTokenType: "1",
|
|
1346
|
+
aleoTokenOwner: "aleo1l3gwacmjruxryy9c7c4fn0acyzprf29hucrvthw7f63lpyhd5y9srydq8z",
|
|
1347
|
+
aleoIsm: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1348
|
+
aleoHook: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1349
|
+
aleoTokenId: "8295938150000417034830036849466229528602563851235385582732969109393809606969field",
|
|
1350
|
+
aleoLocalDecimals: 6,
|
|
1351
|
+
aleoRemoteDecimals: 18,
|
|
1352
|
+
aleoScale: "1000000000000",
|
|
1353
|
+
aleoHyperlaneConfigSource: ALEO_USDT_HYPERLANE_CONFIG_SOURCE
|
|
1354
|
+
};
|
|
1355
|
+
var ALEO_SOL_APP_METADATA = {
|
|
1356
|
+
aleoAppMetadataVerified: true,
|
|
1357
|
+
aleoProgramSource: ALEO_SOL_PROGRAM_SOURCE,
|
|
1358
|
+
aleoAppMetadataSource: ALEO_SOL_APP_METADATA_SOURCE,
|
|
1359
|
+
aleoAppMetadataReviewedAt: "2026-08-17",
|
|
1360
|
+
aleoProgramEdition: 0,
|
|
1361
|
+
aleoTokenType: "1",
|
|
1362
|
+
aleoTokenOwner: "aleo1wr8rfr4ggedjxtg5e23s38zqkgy2j05uc9l8t4akjp5zcw3levpswkwk45",
|
|
1363
|
+
aleoIsm: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1364
|
+
aleoHook: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1365
|
+
aleoTokenId: "6148061383892805373029428966764338809222769879628268522058032128225601478383field",
|
|
1366
|
+
aleoLocalDecimals: 9,
|
|
1367
|
+
aleoRemoteDecimals: 9,
|
|
1368
|
+
aleoHyperlaneConfigSource: ALEO_SOL_HYPERLANE_CONFIG_SOURCE
|
|
1369
|
+
};
|
|
1370
|
+
var ALEO_ETH_REMOTE_ROUTER = {
|
|
1371
|
+
aleoRemoteRouterVerified: true,
|
|
1372
|
+
aleoRemoteRouterSource: ALEO_ETH_REMOTE_ROUTER_SOURCE,
|
|
1373
|
+
aleoRemoteRouterReviewedAt: "2026-08-17",
|
|
1374
|
+
aleoSampleTransferSource: ALEO_ETH_SAMPLE_TRANSFER_SOURCE,
|
|
1375
|
+
aleoDestinationDomain: 1,
|
|
1376
|
+
aleoRemoteRouterEvmAddress: "0x38D447694f5c1f773ae3132cf93bF30B7Ec1Fa5A",
|
|
1377
|
+
aleoRemoteRouterRecipient: "[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 56u8, 212u8, 71u8, 105u8, 79u8, 92u8, 31u8, 119u8, 58u8, 227u8, 19u8, 44u8, 249u8, 59u8, 243u8, 11u8, 126u8, 193u8, 250u8, 90u8]",
|
|
1378
|
+
aleoRemoteRouterGas: "44000",
|
|
1379
|
+
aleoAllowanceSpendersVerified: true,
|
|
1380
|
+
aleoUnusedAllowancesVerified: true,
|
|
1381
|
+
aleoAllowanceSpender0: "aleo194tz0jmyq8rd9htvnqppqw4jqerk2p2zd8plzn3sxl06wcgsm5pq9fka74",
|
|
1382
|
+
aleoAllowanceSpender1: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1383
|
+
aleoAllowanceSpender2: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1384
|
+
aleoAllowanceSpender3: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1385
|
+
aleoAllowanceAmount1: "0",
|
|
1386
|
+
aleoAllowanceAmount2: "0",
|
|
1387
|
+
aleoAllowanceAmount3: "0"
|
|
1388
|
+
};
|
|
1389
|
+
var ALEO_WBTC_REMOTE_ROUTER = {
|
|
1390
|
+
aleoRemoteRouterVerified: true,
|
|
1391
|
+
aleoRemoteRouterSource: ALEO_WBTC_REMOTE_ROUTER_SOURCE,
|
|
1392
|
+
aleoRemoteRouterReviewedAt: "2026-08-17",
|
|
1393
|
+
aleoDestinationDomain: 1,
|
|
1394
|
+
aleoRemoteRouterEvmAddress: "0x20CDC85778b732073F7EecEF3DF25c0d310f8772",
|
|
1395
|
+
aleoRemoteRouterRecipient: "[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 32u8, 205u8, 200u8, 87u8, 120u8, 183u8, 50u8, 7u8, 63u8, 126u8, 236u8, 239u8, 61u8, 242u8, 92u8, 13u8, 49u8, 15u8, 135u8, 114u8]",
|
|
1396
|
+
aleoRemoteRouterGas: "68000",
|
|
1397
|
+
aleoAllowanceSpendersVerified: true,
|
|
1398
|
+
aleoUnusedAllowancesVerified: true,
|
|
1399
|
+
aleoAllowanceSpender0: "aleo194tz0jmyq8rd9htvnqppqw4jqerk2p2zd8plzn3sxl06wcgsm5pq9fka74",
|
|
1400
|
+
aleoAllowanceSpender1: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1401
|
+
aleoAllowanceSpender2: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1402
|
+
aleoAllowanceSpender3: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1403
|
+
aleoAllowanceAmount1: "0",
|
|
1404
|
+
aleoAllowanceAmount2: "0",
|
|
1405
|
+
aleoAllowanceAmount3: "0"
|
|
1406
|
+
};
|
|
1407
|
+
var ALEO_USDT_ETHEREUM_REMOTE_ROUTER = {
|
|
1408
|
+
aleoRemoteRouterVerified: true,
|
|
1409
|
+
aleoRemoteRouterSource: ALEO_USDT_ETHEREUM_REMOTE_ROUTER_SOURCE,
|
|
1410
|
+
aleoRemoteRouterReviewedAt: "2026-08-17",
|
|
1411
|
+
aleoSampleTransferSource: ALEO_USDT_SAMPLE_TRANSFER_SOURCE,
|
|
1412
|
+
aleoSampleTransferDestinationDomain: 56,
|
|
1413
|
+
aleoDestinationDomain: 1,
|
|
1414
|
+
aleoRemoteRouterEvmAddress: "0x3C2064D78e4578E8F936E3db42aEF044E33FBF31",
|
|
1415
|
+
aleoRemoteRouterRecipient: "[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 60u8, 32u8, 100u8, 215u8, 142u8, 69u8, 120u8, 232u8, 249u8, 54u8, 227u8, 219u8, 66u8, 174u8, 240u8, 68u8, 227u8, 63u8, 191u8, 49u8]",
|
|
1416
|
+
aleoRemoteRouterGas: "68000",
|
|
1417
|
+
aleoAllowanceSpendersVerified: true,
|
|
1418
|
+
aleoUnusedAllowancesVerified: true,
|
|
1419
|
+
aleoAllowanceSpender0: "aleo194tz0jmyq8rd9htvnqppqw4jqerk2p2zd8plzn3sxl06wcgsm5pq9fka74",
|
|
1420
|
+
aleoAllowanceSpender1: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1421
|
+
aleoAllowanceSpender2: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1422
|
+
aleoAllowanceSpender3: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1423
|
+
aleoAllowanceAmount1: "0",
|
|
1424
|
+
aleoAllowanceAmount2: "0",
|
|
1425
|
+
aleoAllowanceAmount3: "0"
|
|
1426
|
+
};
|
|
1427
|
+
var ALEO_SOL_REMOTE_ROUTER = {
|
|
1428
|
+
aleoRemoteRouterVerified: true,
|
|
1429
|
+
aleoRemoteRouterSource: ALEO_SOL_REMOTE_ROUTER_SOURCE,
|
|
1430
|
+
aleoRemoteRouterReviewedAt: "2026-08-17",
|
|
1431
|
+
aleoSampleTransitionId: "au15fg39h53h55tkj0nexrme3k6pvgxngxapcyajdhf06jcg3cyeugq5kd7hg",
|
|
1432
|
+
aleoDestinationDomain: 1399811149,
|
|
1433
|
+
aleoRemoteRouterSolanaAddress: "8YGT2pZwyZe94qBpGzWfY2TMEVcwaQ1bXAE7YAgpUaM7",
|
|
1434
|
+
aleoRemoteRouterRecipient: "[112u8, 4u8, 72u8, 22u8, 219u8, 143u8, 68u8, 202u8, 21u8, 197u8, 236u8, 182u8, 198u8, 142u8, 52u8, 96u8, 142u8, 38u8, 51u8, 113u8, 116u8, 143u8, 96u8, 123u8, 104u8, 126u8, 97u8, 73u8, 7u8, 6u8, 211u8, 122u8]",
|
|
1435
|
+
aleoRemoteRouterGas: "300000",
|
|
1436
|
+
aleoAllowanceSpendersVerified: true,
|
|
1437
|
+
aleoUnusedAllowancesVerified: true,
|
|
1438
|
+
aleoAllowanceSpender0: "aleo194tz0jmyq8rd9htvnqppqw4jqerk2p2zd8plzn3sxl06wcgsm5pq9fka74",
|
|
1439
|
+
aleoAllowanceSpender1: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1440
|
+
aleoAllowanceSpender2: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1441
|
+
aleoAllowanceSpender3: "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc",
|
|
1442
|
+
aleoAllowanceAmount1: "0",
|
|
1443
|
+
aleoAllowanceAmount2: "0",
|
|
1444
|
+
aleoAllowanceAmount3: "0"
|
|
1445
|
+
};
|
|
1446
|
+
function route(id, protocol, environment, sourceAssetId, destinationAssetId, availability, deploymentId, metadata2) {
|
|
1447
|
+
return {
|
|
1448
|
+
id,
|
|
1449
|
+
protocol,
|
|
1450
|
+
environment,
|
|
1451
|
+
sourceAssetId,
|
|
1452
|
+
destinationAssetId,
|
|
1453
|
+
availability,
|
|
1454
|
+
deploymentId,
|
|
1455
|
+
source: protocol === "xreserve" ? XRESERVE_SOURCE : HYPERLANE_SOURCE,
|
|
1456
|
+
...metadata2 == null ? {} : { metadata: metadata2 }
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function pair(protocol, environment, left, right, availability, deploymentId, metadata2, reverseAvailability = availability) {
|
|
1460
|
+
return [
|
|
1461
|
+
route(`${protocol}:${left}->${right}`, protocol, environment, left, right, availability, deploymentId, metadata2),
|
|
1462
|
+
route(`${protocol}:${right}->${left}`, protocol, environment, right, left, reverseAvailability, deploymentId, metadata2)
|
|
1463
|
+
];
|
|
1464
|
+
}
|
|
1465
|
+
var routes = [
|
|
1466
|
+
...pair("xreserve", "mainnet", "ethereum/usdc", "aleo/usdcx", "active", "xreserve-usdcx-aleo", {
|
|
1467
|
+
xReserveContract: "0x8888888199b2Df864bf678259607d6D5EBb4e3Ce",
|
|
1468
|
+
sourceChainId: 1,
|
|
1469
|
+
sourceDomain: 0,
|
|
1470
|
+
ethereumDestinationDomain: 0,
|
|
1471
|
+
arcDestinationDomain: 26,
|
|
1472
|
+
remoteDomain: 10002,
|
|
1473
|
+
remoteToken: "usdcx_stablecoin.aleo",
|
|
1474
|
+
remoteTokenBytes32: "0x11ea7dab1d29d5f61500582c63e98c42e1165f9ba050ea9d0c6af9f871987711",
|
|
1475
|
+
minimumAmountAtomic: "2000000",
|
|
1476
|
+
maxFeeAtomic: "100000",
|
|
1477
|
+
bridgeProgram: "usdcx_bridge_v2.aleo",
|
|
1478
|
+
wrapperProgram: "shielded_usdcx_wrapper.aleo",
|
|
1479
|
+
attestationBaseUrl: "https://xreserve-api.circle.com/v1/attestations"
|
|
1480
|
+
}, "active"),
|
|
1481
|
+
...pair("xreserve", "testnet", "sepolia/usdc", "aleo-testnet/usdcx", "active", "xreserve-usdcx-aleo-testnet", {
|
|
1482
|
+
xReserveContract: "0x008888878f94C0d87defdf0B07f46B93C1934442",
|
|
1483
|
+
sourceChainId: 11155111,
|
|
1484
|
+
sourceDomain: 0,
|
|
1485
|
+
ethereumDestinationDomain: 0,
|
|
1486
|
+
arcDestinationDomain: 26,
|
|
1487
|
+
remoteDomain: 10002,
|
|
1488
|
+
remoteToken: "test_usdcx_stablecoin.aleo",
|
|
1489
|
+
remoteTokenBytes32: "0xb143ed52c774cd1d4a519d0e796f15916be5a9e1d45edcd9852dd23f68f53401",
|
|
1490
|
+
minimumAmountAtomic: "2000000",
|
|
1491
|
+
maxFeeAtomic: "100000",
|
|
1492
|
+
bridgeProgram: "test_usdcx_bridge_v2.aleo",
|
|
1493
|
+
wrapperProgram: "shielded_usdcx_wrapper.aleo",
|
|
1494
|
+
attestationBaseUrl: "https://xreserve-api-testnet.circle.com/v1/attestations"
|
|
1495
|
+
}, "active"),
|
|
1496
|
+
route("hyperlane:ethereum/eth->aleo/eth", "hyperlane", "mainnet", "ethereum/eth", "aleo/eth", "active", "ETH/aleo", { ...ETH_HYPERLANE_METADATA, ...ALEO_MAILBOX_METADATA }),
|
|
1497
|
+
route("hyperlane:aleo/eth->ethereum/eth", "hyperlane", "mainnet", "aleo/eth", "ethereum/eth", "metadata-required", "ETH/aleo", { ...ETH_HYPERLANE_METADATA, ...aleoHyperlanePlaceholders("hyp_warp_token_eth_v2.aleo", 1), ...ALEO_ETH_APP_METADATA, ...ALEO_ETH_REMOTE_ROUTER }),
|
|
1498
|
+
route("hyperlane:ethereum/wbtc->aleo/wbtc", "hyperlane", "mainnet", "ethereum/wbtc", "aleo/wbtc", "active", "WBTC/aleo", { ...WBTC_HYPERLANE_METADATA, ...ALEO_MAILBOX_METADATA }),
|
|
1499
|
+
route("hyperlane:aleo/wbtc->ethereum/wbtc", "hyperlane", "mainnet", "aleo/wbtc", "ethereum/wbtc", "metadata-required", "WBTC/aleo", { ...WBTC_HYPERLANE_METADATA, ...aleoHyperlanePlaceholders("hyp_warp_token_wbtc_v2.aleo", 1), ...ALEO_WBTC_APP_METADATA, ...ALEO_WBTC_REMOTE_ROUTER }),
|
|
1500
|
+
route("hyperlane:ethereum/usdt->aleo/usdt", "hyperlane", "mainnet", "ethereum/usdt", "aleo/usdt", "active", "USDT/aleo", { ...USDT_HYPERLANE_METADATA, ...ALEO_MAILBOX_METADATA }),
|
|
1501
|
+
route("hyperlane:aleo/usdt->ethereum/usdt", "hyperlane", "mainnet", "aleo/usdt", "ethereum/usdt", "metadata-required", "USDT/aleo", { ...USDT_HYPERLANE_METADATA, ...aleoHyperlanePlaceholders("hyp_warp_token_usdt_v2.aleo", 1), ...ALEO_USDT_APP_METADATA, ...ALEO_USDT_ETHEREUM_REMOTE_ROUTER }),
|
|
1502
|
+
route("hyperlane:solana/sol->aleo/sol", "hyperlane", "mainnet", "solana/sol", "aleo/sol", "metadata-required", "SOL/aleo", ALEO_MAILBOX_METADATA),
|
|
1503
|
+
route("hyperlane:aleo/sol->solana/sol", "hyperlane", "mainnet", "aleo/sol", "solana/sol", "metadata-required", "SOL/aleo", { ...aleoHyperlanePlaceholders("hyp_warp_token_sol_v2.aleo", 1399811149), ...ALEO_SOL_APP_METADATA, ...ALEO_SOL_REMOTE_ROUTER }),
|
|
1504
|
+
...pair("hyperlane", "mainnet", "aleo/aleo", "ethereum/aleo", "metadata-required", "ALEO/aleo", ALEO_MAILBOX_METADATA),
|
|
1505
|
+
...pair("hyperlane", "mainnet", "aleo/aleo", "solana/aleo", "metadata-required", "ALEO/aleo", ALEO_MAILBOX_METADATA),
|
|
1506
|
+
...pair("hyperlane", "mainnet", "aleo/aleo", "base/aleo", "metadata-required", "ALEO/aleo", ALEO_MAILBOX_METADATA),
|
|
1507
|
+
...pair("hyperlane", "mainnet", "aleo/aleo", "hyperevm/aleo", "metadata-required", "ALEO/aleo", ALEO_MAILBOX_METADATA),
|
|
1508
|
+
route("hyperlane:ethereum/usad->aleo/usad", "hyperlane", "mainnet", "ethereum/usad", "aleo/usad", "metadata-required", "USAD/aleo", ALEO_MAILBOX_METADATA),
|
|
1509
|
+
route("hyperlane:aleo/usad->ethereum/usad", "hyperlane", "mainnet", "aleo/usad", "ethereum/usad", "metadata-required", "USAD/aleo", aleoHyperlanePlaceholders("hyp_warp_token_usad_v2.aleo", 1))
|
|
1510
|
+
];
|
|
1511
|
+
var DEFAULT_BRIDGE_REGISTRY = Object.freeze({
|
|
1512
|
+
version: "2026-08-17.aleo-sol-router.1",
|
|
1513
|
+
chains: Object.freeze(chains),
|
|
1514
|
+
assets: Object.freeze(assets),
|
|
1515
|
+
routes: Object.freeze(routes),
|
|
1516
|
+
sources: Object.freeze([XRESERVE_SOURCE, HYPERLANE_SOURCE])
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
// src/registry/validate.ts
|
|
1520
|
+
function validateBridgeRegistry(registry) {
|
|
1521
|
+
if (!registry.version.trim()) throw new BridgeError("Bridge registry version must not be empty");
|
|
1522
|
+
const chainIds = /* @__PURE__ */ new Set();
|
|
1523
|
+
for (const chain of registry.chains) {
|
|
1524
|
+
if (chainIds.has(chain.id)) throw new BridgeError(`Duplicate bridge chain id: ${chain.id}`);
|
|
1525
|
+
chainIds.add(chain.id);
|
|
1526
|
+
}
|
|
1527
|
+
const assetIds = /* @__PURE__ */ new Set();
|
|
1528
|
+
for (const asset of registry.assets) {
|
|
1529
|
+
if (assetIds.has(asset.id)) throw new BridgeError(`Duplicate bridge asset id: ${asset.id}`);
|
|
1530
|
+
if (!chainIds.has(asset.chainId)) {
|
|
1531
|
+
throw new BridgeError(`Bridge asset ${asset.id} references unknown chain ${asset.chainId}`);
|
|
1532
|
+
}
|
|
1533
|
+
if (!Number.isInteger(asset.decimals) || asset.decimals < 0) {
|
|
1534
|
+
throw new BridgeError(`Bridge asset ${asset.id} has invalid decimals ${asset.decimals}`);
|
|
1535
|
+
}
|
|
1536
|
+
if (asset.addressValidationRegex) {
|
|
1537
|
+
try {
|
|
1538
|
+
new RegExp(asset.addressValidationRegex);
|
|
1539
|
+
} catch (cause) {
|
|
1540
|
+
throw new BridgeError(`Bridge asset ${asset.id} has an invalid address validation regex`, {
|
|
1541
|
+
cause
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
assetIds.add(asset.id);
|
|
1546
|
+
}
|
|
1547
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (const route2 of registry.routes) {
|
|
1549
|
+
if (routeIds.has(route2.id)) throw new BridgeError(`Duplicate bridge route id: ${route2.id}`);
|
|
1550
|
+
if (!assetIds.has(route2.sourceAssetId)) {
|
|
1551
|
+
throw new BridgeError(`Bridge route ${route2.id} references unknown source asset ${route2.sourceAssetId}`);
|
|
1552
|
+
}
|
|
1553
|
+
if (!assetIds.has(route2.destinationAssetId)) {
|
|
1554
|
+
throw new BridgeError(`Bridge route ${route2.id} references unknown destination asset ${route2.destinationAssetId}`);
|
|
1555
|
+
}
|
|
1556
|
+
const source = registry.assets.find((asset) => asset.id === route2.sourceAssetId);
|
|
1557
|
+
const destination = registry.assets.find((asset) => asset.id === route2.destinationAssetId);
|
|
1558
|
+
const sourceChain = registry.chains.find((chain) => chain.id === source.chainId);
|
|
1559
|
+
const destinationChain = registry.chains.find((chain) => chain.id === destination.chainId);
|
|
1560
|
+
if (sourceChain.environment !== route2.environment || destinationChain.environment !== route2.environment) {
|
|
1561
|
+
throw new BridgeError(`Bridge route ${route2.id} crosses registry environments`);
|
|
1562
|
+
}
|
|
1563
|
+
routeIds.add(route2.id);
|
|
1564
|
+
}
|
|
1565
|
+
return registry;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/clients/createBridgeClient.ts
|
|
1569
|
+
function localBridgeTransport() {
|
|
1570
|
+
return createTransport({
|
|
1571
|
+
key: "protocolBridge",
|
|
1572
|
+
name: "Protocol Bridge Transport",
|
|
1573
|
+
type: "protocolBridge",
|
|
1574
|
+
request: async ({ method }) => {
|
|
1575
|
+
throw new Error(`Protocol bridge method is not implemented: ${method}`);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
function createBridgeClient(config = {}) {
|
|
1580
|
+
const {
|
|
1581
|
+
environment = "mainnet",
|
|
1582
|
+
registry = DEFAULT_BRIDGE_REGISTRY,
|
|
1583
|
+
executors = {},
|
|
1584
|
+
xReserveHttpTransport,
|
|
1585
|
+
key = "bridge",
|
|
1586
|
+
name = "Bridge Client"
|
|
1587
|
+
} = config;
|
|
1588
|
+
const validated = validateBridgeRegistry(registry);
|
|
1589
|
+
const client = createClient({ transport: localBridgeTransport(), key, name });
|
|
1590
|
+
return client.extend((inner) => ({
|
|
1591
|
+
environment,
|
|
1592
|
+
registry: validated,
|
|
1593
|
+
...bridgeActions(inner, { environment, registry: validated, executors, xReserveHttpTransport })
|
|
1594
|
+
}));
|
|
1595
|
+
}
|
|
1596
|
+
export {
|
|
1597
|
+
BridgeError,
|
|
1598
|
+
DEFAULT_BRIDGE_REGISTRY,
|
|
1599
|
+
aleoAddressToBytes32,
|
|
1600
|
+
aleoProgramAddress,
|
|
1601
|
+
bridgeActions,
|
|
1602
|
+
buildAleoHyperlaneTransferRemoteCall,
|
|
1603
|
+
buildXReserveBurnCall,
|
|
1604
|
+
buildXReserveDepositPayload,
|
|
1605
|
+
buildXReserveHookData,
|
|
1606
|
+
calculateXReserveDepositNonce,
|
|
1607
|
+
calculateXReserveMessageHash,
|
|
1608
|
+
createBridgeClient,
|
|
1609
|
+
evmAddressToAleoHyperlaneRecipient,
|
|
1610
|
+
evmAddressToXReserveBytes32,
|
|
1611
|
+
executeAleoHyperlaneTransferRemote,
|
|
1612
|
+
executeEvmHyperlaneTransfer,
|
|
1613
|
+
executeEvmXReserveTransfer,
|
|
1614
|
+
executeXReserveBurn,
|
|
1615
|
+
executeXReservePrivateMint,
|
|
1616
|
+
getProtocolAssets as getAssets,
|
|
1617
|
+
getProtocolRoutes as getRoutes,
|
|
1618
|
+
getXReserveAttestation,
|
|
1619
|
+
parseDecimalAmount,
|
|
1620
|
+
prepareTransfer,
|
|
1621
|
+
quoteEvmHyperlaneTransfer,
|
|
1622
|
+
quoteEvmXReserveTransfer,
|
|
1623
|
+
solanaAddressToAleoHyperlaneRecipient,
|
|
1624
|
+
validateBridgeRegistry,
|
|
1625
|
+
xReserveHexToAleoBytes
|
|
1626
|
+
};
|
|
1627
|
+
//# sourceMappingURL=index.js.map
|