@stablecoin.xyz/swap-intent 0.0.0-stage → 0.1.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/README.md +38 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1057 -0
- package/dist/types/engine/evm.d.ts +770 -0
- package/dist/types/engine/order-ttl.d.ts +33 -0
- package/dist/types/engine/svm.d.ts +39 -0
- package/dist/types/mcp/shared/evm-decode.d.ts +26 -0
- package/dist/types/mcp/shared/intent-check.d.ts +64 -0
- package/dist/types/mcp/shared/sign-request.d.ts +101 -0
- package/dist/types/mcp/shared/sol-decode.d.ts +52 -0
- package/dist/types/mcp/worker/build.d.ts +20 -0
- package/dist/types/mcp/worker/order-rail.d.ts +11 -0
- package/dist/types/mcp/worker/routes.d.ts +38 -0
- package/dist/types/mcp/worker/ui-client.d.ts +17 -0
- package/dist/types/packages/swap-intent/src/index.d.ts +8 -0
- package/dist/types/registry.generated.d.ts +1 -0
- package/dist/types/ui-app/src/core/engine.d.ts +275 -0
- package/dist/types/ui-app/src/core/evm.d.ts +371 -0
- package/dist/types/ui-app/src/core/rfq.d.ts +44 -0
- package/dist/types/ui-app/src/core/solana.d.ts +2 -0
- package/dist/types/ui-app/src/core/types.d.ts +143 -0
- package/dist/types/ui-app/src/core/xchain-rails.d.ts +29 -0
- package/package.json +38 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
// ../../mcp/shared/intent-check.ts
|
|
2
|
+
import { formatUnits, getAddress as getAddress2, toHex } from "viem";
|
|
3
|
+
import { PublicKey as PublicKey3 } from "@solana/web3.js";
|
|
4
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";
|
|
5
|
+
|
|
6
|
+
// ../../engine/svm.js
|
|
7
|
+
import { PublicKey } from "@solana/web3.js";
|
|
8
|
+
import { getAddress } from "viem";
|
|
9
|
+
var SETTLE_DISC = {
|
|
10
|
+
open: Buffer.from([228, 220, 155, 71, 199, 189, 60, 45]),
|
|
11
|
+
claim: Buffer.from([62, 198, 214, 193, 213, 159, 108, 210]),
|
|
12
|
+
refund: Buffer.from([2, 96, 183, 251, 63, 208, 46, 46])
|
|
13
|
+
};
|
|
14
|
+
var SETTLE_ORDER_DISC = Buffer.from([134, 173, 223, 185, 77, 86, 28, 51]);
|
|
15
|
+
var ATTEST_DOMAIN = Buffer.from("BRALE_ORIGIN_SETTLE_V1");
|
|
16
|
+
var pad32 = (evmAddr) => Buffer.concat([Buffer.alloc(12), Buffer.from(getAddress(evmAddr).slice(2), "hex")]);
|
|
17
|
+
var evmFrom32 = (b32) => getAddress("0x" + Buffer.from(b32).subarray(12).toString("hex"));
|
|
18
|
+
|
|
19
|
+
// ../../engine/order-ttl.js
|
|
20
|
+
var MAX_ORDER_TTL_SECS = 24 * 60 * 60;
|
|
21
|
+
|
|
22
|
+
// ../../registry.generated.js
|
|
23
|
+
var REGISTRY = { "mainnet": { "tokens": { "SBC": { "base": { "address": "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798", "decimals": 18 }, "solana": { "mint": "DBAzBUXaLj1qANCseUPZz4sp9F8d2sc78C4vKjhbTGMA", "decimals": 9 }, "radius": { "address": "0x33ad9e4BD16B69B5BFdED37D8B5D9fF9aba014Fb", "decimals": 6 }, "brale_value_type": "SBC", "par_usd": true }, "USDC": { "base": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "decimals": 6 }, "solana": { "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "decimals": 6 }, "par_usd": true }, "cfUSD": { "base": { "address": "0x131409B31bF446737dD04353D43DACada544b6fA", "decimals": 6 }, "solana": { "mint": "9Qh5igeYVb9ZC6s6BfNaffycTiNTqv9KhL432LdqyT4Y", "decimals": 9 }, "brale_value_type": "cfUSD", "par_usd": true }, "HSUSD": { "solana": { "mint": "4FVaHEubcqws8hKwJSiW8f8CmKGUyMsBxTKUytcGdRvd", "decimals": 9 }, "brale_value_type": "HSUSD", "par_usd": true }, "rUSD": { "base": { "address": "0xd899C2254C1F4B11FfF038571D6cb02aB8860eC8", "decimals": 6 }, "brale_value_type": "rUSD", "par_usd": true }, "USDGLO": { "base": { "address": "0x4F604735c1cF31399C6E711D5962b2B3E0225AD3", "decimals": 18 }, "brale_value_type": "USDGLO", "par_usd": true } } }, "testnet": { "tokens": { "SBC": { "base": { "address": "0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16", "decimals": 6 }, "radius": { "address": "0x33ad9e4BD16B69B5BFdED37D8B5D9fF9aba014Fb", "decimals": 6 }, "solana": { "mint": "8vnThy8iZjjzxXg1dRagzqp6QZHVqfHT9swUBp5cYsuE", "decimals": 9 }, "brale_value_type": "SBC", "par_usd": true }, "USDC": { "base": { "address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "decimals": 6 }, "solana": { "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", "decimals": 6 }, "par_usd": true }, "cfUSD": { "base": { "address": "0x3E6975D4D85528d1E2cDEbD5FD440AB1C47b6939", "decimals": 6 }, "solana": { "mint": "6EarWgaa1PBoriuCWBSwPatFy1DTNnhXRsq8chE1f2Ja", "decimals": 9 }, "brale_value_type": "cfUSD", "par_usd": true }, "USDGLO": { "base": { "address": "0xE2B10292D34c64C30fc427427207AC285Bb324D1", "decimals": 18 }, "par_usd": true }, "USBC": { "base": { "address": "0x8976F63503366CbF293c144eceD857304e27691C", "decimals": 6 }, "solana": { "mint": "6vW9MMVXj9bi3ctJ9PXDevVgFyqyyL2TXznnMvh6uBBf", "decimals": 6 } }, "SBCX": { "base": { "address": "0xBc22a2a9F310A14ffEA77B87E3BEF0Ed6d912eD4", "decimals": 6 }, "solana": { "mint": "FitjzhLCnp4qezXnFGCWtQxgzps7y7DnsAszEH1WG9vi", "decimals": 6 } }, "OUSD": { "base": { "address": "0xC38D649aE6dFe9F689D4d35288d48fD55D4D2aDf", "decimals": 6 }, "solana": { "mint": "9i2S2wkqhQ2K6trHVA4aCSyPqqKmAagBC6GimhwWPYvn", "decimals": 6 } }, "MOCK_SBC": { "solana": { "mint": "DRTscPBHTYyUs2buH5tuuFtxJcY1Jdtag82KsPrsbBcr", "decimals": 9 } } } } };
|
|
24
|
+
|
|
25
|
+
// ../../mcp/shared/addresses.generated.json
|
|
26
|
+
var addresses_generated_default = {
|
|
27
|
+
mainnet: {
|
|
28
|
+
base: {
|
|
29
|
+
chainId: 8453,
|
|
30
|
+
escrow: "0x4F0c8045479DD9163977f3511c11a13D0dD666da",
|
|
31
|
+
rfqRouter: "0x8A62446bDbf0Df502a1C7fb2BBA31Fc69296d536",
|
|
32
|
+
permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
|
|
33
|
+
solver: "0x75770a6c6746D8B5B1C82c149B61B5bBc3dEDDe5"
|
|
34
|
+
},
|
|
35
|
+
radius: {
|
|
36
|
+
chainId: 723487,
|
|
37
|
+
escrow: "0xd6b99696C2A1363b30B47b850027d38fe177FC55"
|
|
38
|
+
},
|
|
39
|
+
solana: {
|
|
40
|
+
dstChainId: 900,
|
|
41
|
+
originSettler: "5MwMD5AbUDd2s5E4TSa3GvMKRUKQWxpKgH8LHWFXgqoY",
|
|
42
|
+
solver: "BvVi9qmmthHMwvbWLVwS9HefZ3fHVD3jAv5rygHJAkzG"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
testnet: {
|
|
46
|
+
base: {
|
|
47
|
+
chainId: 84532,
|
|
48
|
+
escrow: "0xb5C3ac0a0e0D55af011f7BfE667EA99f936dA76c",
|
|
49
|
+
rfqRouter: "0xE223A90bb254F17e48120653678cD96dB877D349",
|
|
50
|
+
permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
|
|
51
|
+
solver: "0x75770a6c6746D8B5B1C82c149B61B5bBc3dEDDe5"
|
|
52
|
+
},
|
|
53
|
+
radius: {
|
|
54
|
+
chainId: 72344,
|
|
55
|
+
escrow: "0x7B90cd94e7cDA6c0391A8d9a51De53B9aa2E9ee3"
|
|
56
|
+
},
|
|
57
|
+
solana: {
|
|
58
|
+
dstChainId: 901,
|
|
59
|
+
originSettler: "5MwMD5AbUDd2s5E4TSa3GvMKRUKQWxpKgH8LHWFXgqoY",
|
|
60
|
+
solver: "BvVi9qmmthHMwvbWLVwS9HefZ3fHVD3jAv5rygHJAkzG",
|
|
61
|
+
sbcMint: "4VAdTvRcaDWCBU3kYpo8Xg8gtAqBCfCVu2jJ1r3PwU8j"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ../../mcp/shared/sign-request.ts
|
|
67
|
+
function loadManifest() {
|
|
68
|
+
return addresses_generated_default;
|
|
69
|
+
}
|
|
70
|
+
var INT_TYPE = /^u?int\d*$/;
|
|
71
|
+
var ARRAY_TYPE = /^(.*)\[\d*\]$/;
|
|
72
|
+
function hydrateValue(type, value, types) {
|
|
73
|
+
const arr = ARRAY_TYPE.exec(type);
|
|
74
|
+
if (arr) return Array.isArray(value) ? value.map((x) => hydrateValue(arr[1], x, types)) : value;
|
|
75
|
+
if (INT_TYPE.test(type)) return BigInt(value);
|
|
76
|
+
const fields = types[type];
|
|
77
|
+
if (fields && value && typeof value === "object") {
|
|
78
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, hydrateValue(fields.find((f) => f.name === k)?.type ?? "", v, types)]));
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function hydrateTypedData(td) {
|
|
83
|
+
const message = hydrateValue(td.primaryType, td.message, td.types);
|
|
84
|
+
const { EIP712Domain: _omit, ...types } = td.types;
|
|
85
|
+
return { domain: td.domain, types, primaryType: td.primaryType, message };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ../../mcp/shared/sol-decode.ts
|
|
89
|
+
import { ComputeBudgetProgram, PublicKey as PublicKey2, SystemProgram, VersionedTransaction } from "@solana/web3.js";
|
|
90
|
+
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
91
|
+
var TOKEN_PROGRAMS = /* @__PURE__ */ new Set([TOKEN_PROGRAM_ID.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()]);
|
|
92
|
+
var SYSTEM = SystemProgram.programId.toBase58();
|
|
93
|
+
var COMPUTE_BUDGET = ComputeBudgetProgram.programId.toBase58();
|
|
94
|
+
var TRANSFER = 3;
|
|
95
|
+
var TRANSFER_CHECKED = 12;
|
|
96
|
+
var SYSTEM_TRANSFER = 2;
|
|
97
|
+
var view = (d) => new DataView(d.buffer, d.byteOffset, d.byteLength);
|
|
98
|
+
var u32 = (d, o) => view(d).getUint32(o, true);
|
|
99
|
+
var u64 = (d, o) => view(d).getBigUint64(o, true);
|
|
100
|
+
var i64 = (d, o) => view(d).getBigInt64(o, true);
|
|
101
|
+
var hex = (d) => Array.from(d, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
102
|
+
var sameBytes = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
103
|
+
var fromBase64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
104
|
+
function decodeIx(program, accounts, data, signers) {
|
|
105
|
+
const ix = { program, accounts, signers };
|
|
106
|
+
if (TOKEN_PROGRAMS.has(program) && data[0] === TRANSFER_CHECKED && data.length >= 10) {
|
|
107
|
+
ix.transfer = { program, source: accounts[0], mint: accounts[1], dest: accounts[2], authority: accounts[3], amount: u64(data, 1) };
|
|
108
|
+
} else if (TOKEN_PROGRAMS.has(program) && data[0] === TRANSFER && data.length >= 9) {
|
|
109
|
+
ix.transfer = { program, source: accounts[0], mint: null, dest: accounts[1], authority: accounts[2], amount: u64(data, 1) };
|
|
110
|
+
} else if (program === SYSTEM && data.length >= 12 && u32(data, 0) === SYSTEM_TRANSFER) {
|
|
111
|
+
ix.lamports = { from: accounts[0], to: accounts[1], amount: u64(data, 4) };
|
|
112
|
+
} else if (program === COMPUTE_BUDGET && data[0] === 2 && data.length >= 5) {
|
|
113
|
+
ix.computeBudget = { limit: u32(data, 1) };
|
|
114
|
+
} else if (program === COMPUTE_BUDGET && data[0] === 3 && data.length >= 9) {
|
|
115
|
+
ix.computeBudget = { microLamports: u64(data, 1) };
|
|
116
|
+
} else if (data.length >= 184 && sameBytes(data.subarray(0, 8), SETTLE_DISC.open)) {
|
|
117
|
+
ix.open = {
|
|
118
|
+
program,
|
|
119
|
+
user: accounts[0],
|
|
120
|
+
inputMint: accounts[1],
|
|
121
|
+
userAta: accounts[3],
|
|
122
|
+
tokenProgram: accounts[6],
|
|
123
|
+
solver: new PublicKey2(data.subarray(40, 72)).toBase58(),
|
|
124
|
+
inputAmount: u64(data, 72),
|
|
125
|
+
outputToken: "0x" + hex(data.subarray(80, 112)),
|
|
126
|
+
outputAmount: u64(data, 112) + (u64(data, 120) << 64n),
|
|
127
|
+
recipientEvm: evmFrom32(data.subarray(128, 160)),
|
|
128
|
+
dstChainId: Number(u64(data, 160)),
|
|
129
|
+
fillDeadline: i64(data, 168),
|
|
130
|
+
expires: i64(data, 176)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return ix;
|
|
134
|
+
}
|
|
135
|
+
async function decodeSolRequest(req, deps) {
|
|
136
|
+
const msg = VersionedTransaction.deserialize(fromBase64(req.txBase64)).message;
|
|
137
|
+
const lookups = "addressTableLookups" in msg ? msg.addressTableLookups : [];
|
|
138
|
+
const alts = lookups.length ? await deps.loadAlts(lookups.map((l) => l.accountKey), req.net) : [];
|
|
139
|
+
const keys = msg.getAccountKeys({ addressLookupTableAccounts: alts });
|
|
140
|
+
const nSig = msg.header.numRequiredSignatures;
|
|
141
|
+
const at = (i) => {
|
|
142
|
+
const k = keys.get(i);
|
|
143
|
+
if (!k) throw new Error("refused: transaction references an account that could not be resolved");
|
|
144
|
+
return k.toBase58();
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
feePayer: at(0),
|
|
148
|
+
instructions: msg.compiledInstructions.map(
|
|
149
|
+
(ci) => decodeIx(at(ci.programIdIndex), ci.accountKeyIndexes.map(at), ci.data, ci.accountKeyIndexes.filter((i) => i < nSig).map(at))
|
|
150
|
+
)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ../../mcp/shared/intent-check.ts
|
|
155
|
+
var DEFAULT_LIMITS = { maxRedeemSlippageBps: 150, maxSolPriorityFeeLamports: 2e6 };
|
|
156
|
+
var GAS_FLOOR_PCT = 97n;
|
|
157
|
+
var isGasRoute = (req) => req.kind === "sol-tx" ? req.route === "gas-sol" : req.kind === "evm-permit";
|
|
158
|
+
var gasQuoteQuery = (req, spend) => ({ chain: req.kind === "sol-tx" ? "solana" : "base", in: req.inSym, amount: formatUnits(spend.units, spend.decimals) });
|
|
159
|
+
function gasSpend(p) {
|
|
160
|
+
const { req } = p;
|
|
161
|
+
if (!isGasRoute(req)) return void 0;
|
|
162
|
+
const decimals = token(req.net, req.inSym, sourceChain(req))?.decimals;
|
|
163
|
+
if (decimals === void 0) return void 0;
|
|
164
|
+
if (req.kind !== "sol-tx") return p.evm ? { units: p.evm.inputAmount, decimals } : void 0;
|
|
165
|
+
const me = p.self.sol;
|
|
166
|
+
const mine = me && p.sol ? userInstructions(p.sol, me) : [];
|
|
167
|
+
const pay = mine.length === 1 ? mine[0].transfer : void 0;
|
|
168
|
+
return pay ? { units: pay.amount, decimals } : void 0;
|
|
169
|
+
}
|
|
170
|
+
var spendMatchesDeclared = (s, uiAmount) => {
|
|
171
|
+
const spent = Number(formatUnits(s.units, s.decimals));
|
|
172
|
+
return Math.abs(spent - uiAmount) <= 1e-6 + uiAmount * 1e-9;
|
|
173
|
+
};
|
|
174
|
+
function gasFloorOf(quote) {
|
|
175
|
+
if (quote.fillable === false) throw new Error("gas top-up is not fillable right now");
|
|
176
|
+
try {
|
|
177
|
+
return BigInt(quote.outBaseUnits) * GAS_FLOOR_PCT / 100n;
|
|
178
|
+
} catch {
|
|
179
|
+
throw new Error("gas quote unavailable");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
var MIN_DEADLINE_S = 60;
|
|
183
|
+
var MAX_DEADLINE_AHEAD_S = 7200;
|
|
184
|
+
var TTL_SKEW_S = 600;
|
|
185
|
+
var DEFAULT_CU_PER_IX = 2e5;
|
|
186
|
+
var MAX_CU = 14e5;
|
|
187
|
+
var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
|
|
188
|
+
var scale = (amount, from, to) => to >= from ? amount * 10n ** BigInt(to - from) : amount / 10n ** BigInt(from - to);
|
|
189
|
+
var lessBps = (amount, bps) => amount * BigInt(1e4 - bps) / 10000n;
|
|
190
|
+
var nowS = (p) => BigInt(Math.floor(p.now / 1e3));
|
|
191
|
+
function orderWindow(p, expires, fillDeadline) {
|
|
192
|
+
need(expires <= nowS(p) + BigInt(MAX_ORDER_TTL_SECS + TTL_SKEW_S), "deadline-too-far");
|
|
193
|
+
need(fillDeadline <= expires, "deadline-too-far");
|
|
194
|
+
}
|
|
195
|
+
var Refuse = class {
|
|
196
|
+
reason;
|
|
197
|
+
constructor(reason) {
|
|
198
|
+
this.reason = reason;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function need(cond, reason) {
|
|
202
|
+
if (!cond) throw new Refuse(reason);
|
|
203
|
+
}
|
|
204
|
+
var sameEvm = (a, b) => !!a && !!b && getAddress2(a) === getAddress2(b);
|
|
205
|
+
function token(net, sym, chain) {
|
|
206
|
+
const tokens = REGISTRY[net]?.tokens ?? {};
|
|
207
|
+
const key = Object.keys(tokens).find((k) => k.toUpperCase() === sym.toUpperCase());
|
|
208
|
+
const t = key ? tokens[key]?.[chain] : void 0;
|
|
209
|
+
const sbcMint = loadManifest()[net]?.solana.sbcMint;
|
|
210
|
+
return t && chain === "solana" && key.toUpperCase() === "SBC" && sbcMint ? { ...t, mint: sbcMint } : t;
|
|
211
|
+
}
|
|
212
|
+
function userInstructions(s, me) {
|
|
213
|
+
const harmless = (ix) => ix.program === ASSOCIATED_TOKEN_PROGRAM_ID.toBase58() && ix.accounts[0] !== me || ix.lamports !== void 0 && ix.lamports.from !== me;
|
|
214
|
+
return s.instructions.filter((ix) => ix.signers.includes(me) && !harmless(ix));
|
|
215
|
+
}
|
|
216
|
+
var sourceChain = (req) => req.kind === "sol-tx" ? "solana" : req.chain;
|
|
217
|
+
function ata(mint, owner, program) {
|
|
218
|
+
return getAssociatedTokenAddressSync(new PublicKey3(mint), new PublicKey3(owner), true, new PublicKey3(program)).toBase58();
|
|
219
|
+
}
|
|
220
|
+
function quotedOutOf(req) {
|
|
221
|
+
try {
|
|
222
|
+
return BigInt(String(req.submitBody.quotedOut));
|
|
223
|
+
} catch {
|
|
224
|
+
throw new Refuse("output-below-floor");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
var limits = (p) => p.limits ?? DEFAULT_LIMITS;
|
|
228
|
+
function evmChecks(p, e) {
|
|
229
|
+
const { req, self } = p;
|
|
230
|
+
const m = loadManifest()[req.net];
|
|
231
|
+
const chain = sourceChain(req);
|
|
232
|
+
const inTok = token(req.net, req.inSym, chain);
|
|
233
|
+
const chainId = chain === "radius" ? m.radius?.chainId : m.base.chainId;
|
|
234
|
+
need(e.chainId === chainId, "wrong-chain");
|
|
235
|
+
need(sameEvm(e.inputToken, inTok.address), "unknown-token");
|
|
236
|
+
const out = { units: e.inputAmount, destChain: chain, recipient: e.recipient, deadline: e.deadline, expires: e.expires };
|
|
237
|
+
if (req.kind === "evm-rfq") {
|
|
238
|
+
need(sameEvm(e.target, m.base.rfqRouter), "unknown-target");
|
|
239
|
+
need(sameEvm(e.spender, m.base.rfqRouter), "unknown-spender");
|
|
240
|
+
need(sameEvm(e.solver, m.base.solver), "unknown-solver");
|
|
241
|
+
const outTok = token(req.net, req.outSym, "base");
|
|
242
|
+
need(sameEvm(e.outputToken, outTok?.address), "unknown-token");
|
|
243
|
+
const par = scale(e.inputAmount, inTok.decimals, outTok.decimals);
|
|
244
|
+
need(e.minOutput >= (req.route === "base-redeem" ? lessBps(par, limits(p).maxRedeemSlippageBps) : par), "output-below-floor");
|
|
245
|
+
out.destChain = "base";
|
|
246
|
+
out.outUnits = e.minOutput;
|
|
247
|
+
out.outDecimals = outTok.decimals;
|
|
248
|
+
} else if (req.kind === "evm-permit") {
|
|
249
|
+
need(sameEvm(e.spender, m.base.solver), "unknown-spender");
|
|
250
|
+
need(p.minOut !== void 0, "gas-floor-missing");
|
|
251
|
+
need(quotedOutOf(req) >= p.minOut, "output-below-floor");
|
|
252
|
+
} else {
|
|
253
|
+
const escrow = chain === "radius" ? m.radius?.escrow : m.base.escrow;
|
|
254
|
+
need(sameEvm(e.target, escrow), "unknown-target");
|
|
255
|
+
need(sameEvm(e.destinationSettler, ZERO_ADDR), "unknown-target");
|
|
256
|
+
need(sameEvm(e.filler, m.base.solver), "unknown-solver");
|
|
257
|
+
let outDec;
|
|
258
|
+
let destChain;
|
|
259
|
+
if (e.recipientVm === "svm") {
|
|
260
|
+
need(e.destinationChainId === BigInt(m.solana.dstChainId), "wrong-chain");
|
|
261
|
+
const outT = token(req.net, req.outSym, "solana");
|
|
262
|
+
need(outT?.mint && e.outputToken === toHex(new PublicKey3(outT.mint).toBytes()), "unknown-token");
|
|
263
|
+
outDec = outT.decimals;
|
|
264
|
+
destChain = "solana";
|
|
265
|
+
} else {
|
|
266
|
+
const dst = e.destinationChainId === BigInt(m.base.chainId) ? "base" : e.destinationChainId === BigInt(m.radius?.chainId ?? -1) ? "radius" : null;
|
|
267
|
+
need(dst && dst !== chain, "wrong-chain");
|
|
268
|
+
const outT = token(req.net, req.outSym, dst);
|
|
269
|
+
need(outT?.address && String(e.outputToken).toLowerCase() === toHex(pad32(outT.address)).toLowerCase(), "unknown-token");
|
|
270
|
+
outDec = outT.decimals;
|
|
271
|
+
destChain = dst;
|
|
272
|
+
}
|
|
273
|
+
need(e.outputAmount >= scale(e.inputAmount, inTok.decimals, outDec), "output-below-floor");
|
|
274
|
+
orderWindow(p, e.expires, e.fillDeadline);
|
|
275
|
+
out.destChain = destChain;
|
|
276
|
+
out.outUnits = e.outputAmount;
|
|
277
|
+
out.outDecimals = outDec;
|
|
278
|
+
}
|
|
279
|
+
need(sameEvm(e.owner, self.evm), "owner-not-self");
|
|
280
|
+
need(e.recipientVm === "evm" ? sameEvm(e.recipient, self.evm) : !!self.sol && e.recipient === self.sol, "recipient-not-self");
|
|
281
|
+
need(e.deadline === void 0 || e.deadline >= nowS(p) + BigInt(MIN_DEADLINE_S), "deadline-too-close");
|
|
282
|
+
need(e.deadline === void 0 || e.deadline <= nowS(p) + BigInt(MAX_DEADLINE_AHEAD_S), "deadline-too-far");
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
function solChecks(p, s) {
|
|
286
|
+
const { req, self } = p;
|
|
287
|
+
need(req.kind === "sol-tx", "unexpected-instruction");
|
|
288
|
+
const m = loadManifest()[req.net];
|
|
289
|
+
const me = self.sol;
|
|
290
|
+
need(me, "owner-not-self");
|
|
291
|
+
const mine = userInstructions(s, me);
|
|
292
|
+
need(mine.length === 1, "unexpected-instruction");
|
|
293
|
+
const ix = mine[0];
|
|
294
|
+
const inMint = token(req.net, req.inSym, "solana").mint;
|
|
295
|
+
const inDec = token(req.net, req.inSym, "solana").decimals;
|
|
296
|
+
if (req.route === "xchain-sol-origin") {
|
|
297
|
+
need(ix.open, "unexpected-instruction");
|
|
298
|
+
const o = ix.open;
|
|
299
|
+
need(o.program === m.solana.originSettler, "unknown-target");
|
|
300
|
+
need(s.instructions.every((i) => i.program === m.solana.originSettler || i.program === COMPUTE_BUDGET), "unexpected-instruction");
|
|
301
|
+
const limit = s.instructions.find((i) => i.computeBudget?.limit !== void 0)?.computeBudget?.limit ?? Math.min(MAX_CU, DEFAULT_CU_PER_IX * s.instructions.filter((i) => i.program !== COMPUTE_BUDGET).length);
|
|
302
|
+
const price = s.instructions.find((i) => i.computeBudget?.microLamports !== void 0)?.computeBudget?.microLamports ?? 0n;
|
|
303
|
+
need(BigInt(limit) * price / 1000000n <= BigInt(limits(p).maxSolPriorityFeeLamports), "fee-too-high");
|
|
304
|
+
need(o.user === me, "owner-not-self");
|
|
305
|
+
need(o.userAta === ata(inMint, me, o.tokenProgram), "unexpected-instruction");
|
|
306
|
+
need(o.solver === m.solana.solver, "unknown-solver");
|
|
307
|
+
need(o.inputMint === inMint, "unknown-token");
|
|
308
|
+
const dst = o.dstChainId === m.base.chainId ? "base" : o.dstChainId === m.radius?.chainId ? "radius" : null;
|
|
309
|
+
need(dst, "wrong-chain");
|
|
310
|
+
const out = token(req.net, req.outSym, dst);
|
|
311
|
+
need(out?.address && o.outputToken.toLowerCase() === toHex(pad32(out.address)).toLowerCase(), "unknown-token");
|
|
312
|
+
need(sameEvm(o.recipientEvm, self.evm), "recipient-not-self");
|
|
313
|
+
need(o.outputAmount >= scale(o.inputAmount, inDec, out.decimals), "output-below-floor");
|
|
314
|
+
orderWindow(p, o.expires, o.fillDeadline);
|
|
315
|
+
return { units: o.inputAmount, outUnits: o.outputAmount, outDecimals: out.decimals, destChain: dst, recipient: o.recipientEvm, expires: o.expires };
|
|
316
|
+
}
|
|
317
|
+
need(s.feePayer !== me, "user-is-fee-payer");
|
|
318
|
+
need(ix.transfer && ix.transfer.authority === me && ix.transfer.mint, "unexpected-instruction");
|
|
319
|
+
const pay = ix.transfer;
|
|
320
|
+
need(pay.mint === inMint, "unknown-token");
|
|
321
|
+
need(pay.source === ata(inMint, me, pay.program), "unexpected-instruction");
|
|
322
|
+
need(pay.dest === ata(inMint, m.solana.solver, pay.program), "unknown-spender");
|
|
323
|
+
const res = { units: pay.amount, destChain: "solana", recipient: me };
|
|
324
|
+
if (req.route === "gas-sol") {
|
|
325
|
+
need(s.instructions.some((i) => i.lamports && i.lamports.to === me && i.lamports.amount > 0n), "recipient-not-self");
|
|
326
|
+
res.outUnits = s.instructions.reduce((a, i) => i.lamports && i.lamports.to === me ? a + i.lamports.amount : a, 0n);
|
|
327
|
+
res.outDecimals = 9;
|
|
328
|
+
need(p.minOut !== void 0, "gas-floor-missing");
|
|
329
|
+
need(res.outUnits >= p.minOut, "output-below-floor");
|
|
330
|
+
} else {
|
|
331
|
+
const outT = token(req.net, req.outSym, "solana");
|
|
332
|
+
need(outT?.mint, "unknown-token");
|
|
333
|
+
const par = scale(pay.amount, inDec, outT.decimals);
|
|
334
|
+
const floor = req.route === "sol-redeem" ? lessBps(par, limits(p).maxRedeemSlippageBps) : par;
|
|
335
|
+
const credits = s.instructions.filter((i) => i.transfer && i.transfer.mint === outT.mint && i.transfer.dest === ata(outT.mint, me, i.transfer.program));
|
|
336
|
+
need(credits.length > 0, "recipient-not-self");
|
|
337
|
+
const credited = credits.reduce((a, i) => a + i.transfer.amount, 0n);
|
|
338
|
+
need(credited >= floor, "output-below-floor");
|
|
339
|
+
res.outUnits = credited;
|
|
340
|
+
res.outDecimals = outT.decimals;
|
|
341
|
+
}
|
|
342
|
+
return res;
|
|
343
|
+
}
|
|
344
|
+
function checkIntent(p) {
|
|
345
|
+
try {
|
|
346
|
+
const { req } = p;
|
|
347
|
+
const inTok = token(req.net, req.inSym, sourceChain(req));
|
|
348
|
+
need(inTok && (inTok.address || inTok.mint), "unknown-token");
|
|
349
|
+
const checked = req.kind === "sol-tx" ? (need(p.sol, "unexpected-instruction"), solChecks(p, p.sol)) : (need(p.evm, "unexpected-instruction"), evmChecks(p, p.evm));
|
|
350
|
+
need(spendMatchesDeclared({ units: checked.units, decimals: inTok.decimals }, req.uiAmount), "amount-mismatch");
|
|
351
|
+
return { ok: true, decimals: inTok.decimals, ...checked };
|
|
352
|
+
} catch (e) {
|
|
353
|
+
if (e instanceof Refuse) return { ok: false, reason: e.reason };
|
|
354
|
+
throw e;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ../../mcp/shared/evm-decode.ts
|
|
359
|
+
import { decodeAbiParameters, decodeFunctionData, getAddress as getAddress4, hexToBytes } from "viem";
|
|
360
|
+
import { PublicKey as PublicKey4 } from "@solana/web3.js";
|
|
361
|
+
|
|
362
|
+
// ../../ui-app/src/core/evm.ts
|
|
363
|
+
var PERMIT_TYPES = {
|
|
364
|
+
Permit: [
|
|
365
|
+
{ name: "owner", type: "address" },
|
|
366
|
+
{ name: "spender", type: "address" },
|
|
367
|
+
{ name: "value", type: "uint256" },
|
|
368
|
+
{ name: "nonce", type: "uint256" },
|
|
369
|
+
{ name: "deadline", type: "uint256" }
|
|
370
|
+
]
|
|
371
|
+
};
|
|
372
|
+
var RFQ_ORDER_TYPES = {
|
|
373
|
+
RfqOrder: [
|
|
374
|
+
{ name: "user", type: "address" },
|
|
375
|
+
{ name: "inputToken", type: "address" },
|
|
376
|
+
{ name: "inputAmount", type: "uint256" },
|
|
377
|
+
{ name: "outputToken", type: "address" },
|
|
378
|
+
{ name: "minOutputAmount", type: "uint256" },
|
|
379
|
+
{ name: "solver", type: "address" },
|
|
380
|
+
{ name: "deadline", type: "uint256" },
|
|
381
|
+
{ name: "nonce", type: "uint256" }
|
|
382
|
+
]
|
|
383
|
+
};
|
|
384
|
+
function rfqDomain(router, chainId) {
|
|
385
|
+
return { name: "BraleRfqSwap", version: "1", chainId, verifyingContract: router };
|
|
386
|
+
}
|
|
387
|
+
function rfqOrderTypedData(router, chainId, order) {
|
|
388
|
+
return {
|
|
389
|
+
domain: rfqDomain(router, chainId),
|
|
390
|
+
types: RFQ_ORDER_TYPES,
|
|
391
|
+
primaryType: "RfqOrder",
|
|
392
|
+
message: order
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
var ERC20_ABI = [
|
|
396
|
+
{ type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
|
|
397
|
+
{ type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
|
|
398
|
+
{ type: "function", name: "allowance", stateMutability: "view", inputs: [{ type: "address" }, { type: "address" }], outputs: [{ type: "uint256" }] },
|
|
399
|
+
{ type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] }
|
|
400
|
+
];
|
|
401
|
+
var EVM_OPEN_ABI = [
|
|
402
|
+
{
|
|
403
|
+
type: "function",
|
|
404
|
+
name: "open",
|
|
405
|
+
stateMutability: "nonpayable",
|
|
406
|
+
inputs: [
|
|
407
|
+
{
|
|
408
|
+
name: "order",
|
|
409
|
+
type: "tuple",
|
|
410
|
+
components: [
|
|
411
|
+
{ name: "settlementContract", type: "address" },
|
|
412
|
+
{ name: "swapper", type: "address" },
|
|
413
|
+
{ name: "nonce", type: "uint256" },
|
|
414
|
+
{ name: "originChainId", type: "uint256" },
|
|
415
|
+
{ name: "initiateDeadline", type: "uint256" },
|
|
416
|
+
{ name: "fillDeadline", type: "uint256" },
|
|
417
|
+
{ name: "orderDataType", type: "bytes32" },
|
|
418
|
+
{ name: "orderData", type: "bytes" }
|
|
419
|
+
]
|
|
420
|
+
},
|
|
421
|
+
{ name: "signature", type: "bytes" },
|
|
422
|
+
{ name: "fillerData", type: "bytes" }
|
|
423
|
+
],
|
|
424
|
+
outputs: []
|
|
425
|
+
}
|
|
426
|
+
];
|
|
427
|
+
function rehydrateOrder(o) {
|
|
428
|
+
return {
|
|
429
|
+
settlementContract: o.settlementContract,
|
|
430
|
+
swapper: o.swapper,
|
|
431
|
+
nonce: BigInt(o.nonce),
|
|
432
|
+
originChainId: BigInt(o.originChainId),
|
|
433
|
+
initiateDeadline: BigInt(o.initiateDeadline),
|
|
434
|
+
fillDeadline: BigInt(o.fillDeadline),
|
|
435
|
+
orderDataType: o.orderDataType,
|
|
436
|
+
orderData: o.orderData
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ../../engine/evm.js
|
|
441
|
+
import { keccak256, toHex as toHex2, encodeAbiParameters, getAddress as getAddress3 } from "viem";
|
|
442
|
+
import { base, baseSepolia } from "viem/chains";
|
|
443
|
+
var braleOrderDataType = {
|
|
444
|
+
type: "tuple",
|
|
445
|
+
components: [
|
|
446
|
+
{ name: "inputToken", type: "address" },
|
|
447
|
+
{ name: "inputAmount", type: "uint256" },
|
|
448
|
+
{ name: "outputToken", type: "bytes32" },
|
|
449
|
+
{ name: "outputAmount", type: "uint256" },
|
|
450
|
+
{ name: "outputRecipient", type: "bytes32" },
|
|
451
|
+
{ name: "destinationChainId", type: "uint256" },
|
|
452
|
+
{ name: "expires", type: "uint256" },
|
|
453
|
+
{ name: "destinationSettler", type: "address" },
|
|
454
|
+
{ name: "filler", type: "address" }
|
|
455
|
+
// [L-2] swapper-signed filler; ANY_FILLER = open
|
|
456
|
+
]
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// ../../mcp/shared/evm-decode.ts
|
|
460
|
+
function must(cond, msg) {
|
|
461
|
+
if (!cond) throw new Error(`refused: ${msg}`);
|
|
462
|
+
}
|
|
463
|
+
var addr = (v) => getAddress4(String(v));
|
|
464
|
+
function permitFacts(p) {
|
|
465
|
+
must(p?.primaryType === "Permit", "permit typed data is not an EIP-2612 Permit");
|
|
466
|
+
return {
|
|
467
|
+
token: addr(p.domain.verifyingContract),
|
|
468
|
+
chainId: Number(p.domain.chainId),
|
|
469
|
+
owner: addr(p.message.owner),
|
|
470
|
+
spender: addr(p.message.spender),
|
|
471
|
+
value: BigInt(p.message.value),
|
|
472
|
+
deadline: BigInt(p.message.deadline)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function decodeRfq(req) {
|
|
476
|
+
const o = req.orderTypedData;
|
|
477
|
+
must(o?.primaryType === "RfqOrder", "order typed data is not an RfqOrder");
|
|
478
|
+
const m = o.message;
|
|
479
|
+
const p = permitFacts(req.permitTypedData);
|
|
480
|
+
const router = addr(o.domain.verifyingContract);
|
|
481
|
+
const inputToken = addr(m.inputToken), inputAmount = BigInt(m.inputAmount), deadline = BigInt(m.deadline), user = addr(m.user);
|
|
482
|
+
must(p.token === inputToken, "permit token is not the order input token");
|
|
483
|
+
must(p.value === inputAmount, "permit value is not the order input amount");
|
|
484
|
+
must(p.deadline === deadline, "permit deadline is not the order deadline");
|
|
485
|
+
must(p.owner === user, "permit owner is not the order user");
|
|
486
|
+
must(p.chainId === Number(o.domain.chainId), "permit and order are for different chains");
|
|
487
|
+
must(sameOrder(req.order, m), "submitted order differs from the signed order");
|
|
488
|
+
return {
|
|
489
|
+
chainId: Number(o.domain.chainId),
|
|
490
|
+
owner: user,
|
|
491
|
+
spender: p.spender,
|
|
492
|
+
target: router,
|
|
493
|
+
inputToken,
|
|
494
|
+
inputAmount,
|
|
495
|
+
recipient: user,
|
|
496
|
+
recipientVm: "evm",
|
|
497
|
+
solver: addr(m.solver),
|
|
498
|
+
outputToken: addr(m.outputToken),
|
|
499
|
+
deadline,
|
|
500
|
+
minOutput: BigInt(m.minOutputAmount)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function sameOrder(a, m) {
|
|
504
|
+
const keys = ["user", "inputToken", "inputAmount", "outputToken", "minOutputAmount", "solver", "deadline", "nonce"];
|
|
505
|
+
return keys.every((k) => String(a[k]).toLowerCase() === String(m[k]).toLowerCase());
|
|
506
|
+
}
|
|
507
|
+
function decodePermit(req) {
|
|
508
|
+
const p = permitFacts(req.permitTypedData);
|
|
509
|
+
must(String(req.submitBody.user).toLowerCase() === p.owner.toLowerCase(), "gas top-up is for a different wallet than the permit owner");
|
|
510
|
+
return { chainId: p.chainId, owner: p.owner, spender: p.spender, target: null, inputToken: p.token, inputAmount: p.value, recipient: p.owner, recipientVm: "evm", deadline: p.deadline };
|
|
511
|
+
}
|
|
512
|
+
function decodeTxs(req) {
|
|
513
|
+
must(req.txs.length === 2, "expected exactly 2 transactions (approve, open)");
|
|
514
|
+
for (const t of req.txs) must(BigInt(t.value) === 0n, "transactions must carry no native value");
|
|
515
|
+
const [approveTx, openTx] = req.txs;
|
|
516
|
+
const ap = decodeFunctionData({ abi: ERC20_ABI, data: approveTx.data });
|
|
517
|
+
must(ap.functionName === "approve", "first transaction is not an ERC-20 approve");
|
|
518
|
+
const op = decodeFunctionData({ abi: EVM_OPEN_ABI, data: openTx.data });
|
|
519
|
+
must(op.functionName === "open", "second transaction is not escrow open");
|
|
520
|
+
const order = op.args[0];
|
|
521
|
+
const escrow = addr(openTx.to);
|
|
522
|
+
const [od] = decodeAbiParameters([braleOrderDataType], order.orderData);
|
|
523
|
+
const [apSpender, apAmount] = ap.args;
|
|
524
|
+
must(addr(apSpender) === escrow, "approve spender is not the escrow being opened");
|
|
525
|
+
must(addr(approveTx.to) === addr(od.inputToken), "approve token is not the order input token");
|
|
526
|
+
must(apAmount === BigInt(od.inputAmount), "approve amount is not the order input amount");
|
|
527
|
+
must(addr(order.settlementContract) === escrow, "order settlementContract is not the escrow");
|
|
528
|
+
const net = loadManifest()[req.net];
|
|
529
|
+
const recipientBytes = hexToBytes(od.outputRecipient);
|
|
530
|
+
const toSolana = BigInt(od.destinationChainId) === BigInt(net.solana.dstChainId);
|
|
531
|
+
return {
|
|
532
|
+
chainId: Number(order.originChainId),
|
|
533
|
+
owner: addr(order.swapper),
|
|
534
|
+
spender: escrow,
|
|
535
|
+
target: escrow,
|
|
536
|
+
inputToken: addr(od.inputToken),
|
|
537
|
+
inputAmount: BigInt(od.inputAmount),
|
|
538
|
+
recipient: toSolana ? new PublicKey4(recipientBytes).toBase58() : evmFrom32(recipientBytes),
|
|
539
|
+
recipientVm: toSolana ? "svm" : "evm",
|
|
540
|
+
outputToken: od.outputToken,
|
|
541
|
+
destinationChainId: BigInt(od.destinationChainId),
|
|
542
|
+
deadline: BigInt(order.initiateDeadline),
|
|
543
|
+
outputAmount: BigInt(od.outputAmount),
|
|
544
|
+
filler: addr(od.filler),
|
|
545
|
+
destinationSettler: addr(od.destinationSettler),
|
|
546
|
+
expires: BigInt(od.expires),
|
|
547
|
+
fillDeadline: BigInt(order.fillDeadline)
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function decodeEvmRequest(req) {
|
|
551
|
+
if (req.kind === "evm-rfq") return decodeRfq(req);
|
|
552
|
+
if (req.kind === "evm-permit") return decodePermit(req);
|
|
553
|
+
return decodeTxs(req);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ../../mcp/worker/build.ts
|
|
557
|
+
import { encodeFunctionData, getAddress as getAddress5, parseUnits } from "viem";
|
|
558
|
+
|
|
559
|
+
// ../../ui-app/src/core/rfq.ts
|
|
560
|
+
import { formatUnits as formatUnits2 } from "viem";
|
|
561
|
+
var short = (a) => `${a.slice(0, 6)}\u2026${a.slice(-4)}`;
|
|
562
|
+
function buildRfqOrder(p) {
|
|
563
|
+
const order = {
|
|
564
|
+
user: p.user,
|
|
565
|
+
inputToken: p.inputToken,
|
|
566
|
+
inputAmount: p.inputAmount,
|
|
567
|
+
outputToken: p.outputToken,
|
|
568
|
+
minOutputAmount: p.minOutputAmount,
|
|
569
|
+
solver: p.solver,
|
|
570
|
+
deadline: p.deadline,
|
|
571
|
+
nonce: p.nonce
|
|
572
|
+
};
|
|
573
|
+
const typedData = rfqOrderTypedData(p.router, p.chainId, order);
|
|
574
|
+
const confirmRows = rfqConfirmRows(order, p);
|
|
575
|
+
return { order, typedData, confirmRows };
|
|
576
|
+
}
|
|
577
|
+
function rfqConfirmRows(order, meta) {
|
|
578
|
+
const expires = new Date(Number(order.deadline) * 1e3);
|
|
579
|
+
return [
|
|
580
|
+
{ label: "You pay", value: `${formatUnits2(order.inputAmount, meta.inputDecimals)} ${meta.inputSymbol}` },
|
|
581
|
+
{
|
|
582
|
+
label: "You receive at least",
|
|
583
|
+
value: `${formatUnits2(order.minOutputAmount, meta.outputDecimals)} ${meta.outputSymbol}`
|
|
584
|
+
},
|
|
585
|
+
{ label: "Input token", value: short(order.inputToken) },
|
|
586
|
+
{ label: "Output token", value: short(order.outputToken) },
|
|
587
|
+
{ label: "Solver", value: short(order.solver) },
|
|
588
|
+
{ label: "Your nonce", value: order.nonce.toString() },
|
|
589
|
+
{ label: "Signature expires", value: expires.toISOString().replace("T", " ").slice(0, 16) + " UTC" }
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ../../ui-app/src/core/solana.ts
|
|
594
|
+
var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
595
|
+
function b58decode(s) {
|
|
596
|
+
const map = {};
|
|
597
|
+
for (let i = 0; i < B58.length; i++) map[B58[i]] = i;
|
|
598
|
+
let bytes = [0];
|
|
599
|
+
for (const ch of s) {
|
|
600
|
+
if (!(ch in map)) throw new Error(`invalid base58 char '${ch}'`);
|
|
601
|
+
let carry = map[ch];
|
|
602
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
603
|
+
carry += bytes[j] * 58;
|
|
604
|
+
bytes[j] = carry & 255;
|
|
605
|
+
carry >>= 8;
|
|
606
|
+
}
|
|
607
|
+
while (carry) {
|
|
608
|
+
bytes.push(carry & 255);
|
|
609
|
+
carry >>= 8;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
for (const ch of s) {
|
|
613
|
+
if (ch === "1") bytes.push(0);
|
|
614
|
+
else break;
|
|
615
|
+
}
|
|
616
|
+
return Uint8Array.from(bytes.reverse());
|
|
617
|
+
}
|
|
618
|
+
function solToBytes32(addr2) {
|
|
619
|
+
const b = b58decode(addr2.trim());
|
|
620
|
+
if (b.length !== 32) throw new Error(`not a 32-byte Solana address (${b.length} bytes)`);
|
|
621
|
+
return "0x" + [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
622
|
+
}
|
|
623
|
+
function isValidSolAddress(addr2) {
|
|
624
|
+
try {
|
|
625
|
+
solToBytes32(addr2);
|
|
626
|
+
return true;
|
|
627
|
+
} catch {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ../../ui-app/src/core/engine.ts
|
|
633
|
+
var BASE_PAR_OUT = ["SBC", "cfUSD"];
|
|
634
|
+
var SOL_BUY_VAL = { SBC: "SBC", cfUSD: "CFUSD", HSUSD: "HSUSD" };
|
|
635
|
+
var NATIVE_OF = { base: "ETH", solana: "SOL" };
|
|
636
|
+
var isNativeSym = (s) => s === "ETH" || s === "SOL";
|
|
637
|
+
var GAS_CHAINS = Object.keys(NATIVE_OF);
|
|
638
|
+
function engineFor(p) {
|
|
639
|
+
const same2 = p.fromChain === p.toChain;
|
|
640
|
+
if (isNativeSym(p.output)) {
|
|
641
|
+
if (p.output !== NATIVE_OF[p.toChain]) return null;
|
|
642
|
+
if (same2) return "gasBuy";
|
|
643
|
+
return p.flow === "xchain_gas" && GAS_CHAINS.includes(p.fromChain) ? "xchainGas" : null;
|
|
644
|
+
}
|
|
645
|
+
if (p.output === "USDC" && same2) return p.fromChain === "solana" ? "solRedeem" : "baseRedeem";
|
|
646
|
+
if (p.input === "USDC" && same2) {
|
|
647
|
+
if (p.fromChain === "solana" && ["SBC", "cfUSD", "HSUSD"].includes(p.output)) return "solBuy";
|
|
648
|
+
if (p.fromChain === "base" && BASE_PAR_OUT.includes(p.output)) return "baseBuy";
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
if (same2 && p.input !== "USDC" && p.output !== "USDC") {
|
|
652
|
+
if (p.fromChain === "solana") return "solPar";
|
|
653
|
+
if (p.fromChain === "base" && BASE_PAR_OUT.includes(p.output)) return "basePar";
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
if (!same2 && p.fromChain === "base" && p.toChain === "solana" && ["SBC", "USDGLO", "USDC", "cfUSD"].includes(p.input))
|
|
657
|
+
return "intent";
|
|
658
|
+
if (!same2 && p.fromChain === "solana" && p.toChain === "base") return "solOrigin";
|
|
659
|
+
if (!same2 && p.fromChain === "base" && p.toChain === "radius") return "baseToRadius";
|
|
660
|
+
if (!same2 && p.fromChain === "radius" && p.toChain === "base") return "radiusToBase";
|
|
661
|
+
if (!same2 && p.fromChain === "radius" && p.toChain === "solana" && p.input === "SBC" && p.output === "SBC") return "radiusToSolana";
|
|
662
|
+
if (!same2 && p.fromChain === "solana" && p.toChain === "radius" && p.input === "SBC" && p.output === "SBC") return "solanaToRadius";
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
function xchainRecipientValid(engine, recipient) {
|
|
666
|
+
const v = (recipient || "").trim();
|
|
667
|
+
switch (engine) {
|
|
668
|
+
case "intent":
|
|
669
|
+
case "radiusToSolana":
|
|
670
|
+
return isValidSolAddress(v);
|
|
671
|
+
case "solOrigin":
|
|
672
|
+
case "baseToRadius":
|
|
673
|
+
case "radiusToBase":
|
|
674
|
+
case "solanaToRadius":
|
|
675
|
+
return isEvmAddress(v);
|
|
676
|
+
default:
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function gasPairsFrom(caps) {
|
|
681
|
+
const flow = caps.flows?.gas_topup;
|
|
682
|
+
if (!flow?.enabled) return [];
|
|
683
|
+
const assets = caps.assets || {};
|
|
684
|
+
const allowedIn = new Set(flow.inputs || []);
|
|
685
|
+
const out = [];
|
|
686
|
+
for (const chain of ["base", "solana"]) {
|
|
687
|
+
if (!(flow.chains || []).includes(chain)) continue;
|
|
688
|
+
const native = NATIVE_OF[chain];
|
|
689
|
+
if (!native) continue;
|
|
690
|
+
if (!(flow.outputs || []).includes(native)) continue;
|
|
691
|
+
for (const [sym, a] of Object.entries(assets)) {
|
|
692
|
+
if (!allowedIn.has(sym)) continue;
|
|
693
|
+
if (!a?.enabled || !a?.input || !(a.chains || []).includes(chain)) continue;
|
|
694
|
+
out.push({ input: sym, fromChain: chain, output: native, toChain: chain, flow: "gas_topup" });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
function bridgeStableFor(caps, input, from, to) {
|
|
700
|
+
const xg = caps.flows?.xchain_gas;
|
|
701
|
+
const gas = caps.flows?.gas_topup;
|
|
702
|
+
if (!xg?.enabled || !gas?.enabled) return null;
|
|
703
|
+
if (from === to || !GAS_CHAINS.includes(from) || !GAS_CHAINS.includes(to)) return null;
|
|
704
|
+
if (!(gas.chains || []).includes(to)) return null;
|
|
705
|
+
if (!(xg.corridors || []).some((c) => c.from === from && c.to === to)) return null;
|
|
706
|
+
if (!(xg.inputs || []).includes(input)) return null;
|
|
707
|
+
const assets = caps.assets || {};
|
|
708
|
+
const payable = new Set(gas.inputs || []);
|
|
709
|
+
const usable = (sym) => {
|
|
710
|
+
const a = assets[sym];
|
|
711
|
+
return !!a?.enabled && !!a?.output && (a.chains || []).includes(to) && payable.has(sym);
|
|
712
|
+
};
|
|
713
|
+
if (usable(input)) return input;
|
|
714
|
+
for (const sym of Object.keys(assets)) if (usable(sym)) return sym;
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
function xchainGasPairsFrom(caps) {
|
|
718
|
+
const xg = caps.flows?.xchain_gas;
|
|
719
|
+
if (!xg?.enabled) return [];
|
|
720
|
+
const assets = caps.assets || {};
|
|
721
|
+
const out = [];
|
|
722
|
+
for (const c of xg.corridors || []) {
|
|
723
|
+
const from = c.from;
|
|
724
|
+
const to = c.to;
|
|
725
|
+
if (from === to || !GAS_CHAINS.includes(from) || !GAS_CHAINS.includes(to)) continue;
|
|
726
|
+
const native = NATIVE_OF[to];
|
|
727
|
+
if (!native) continue;
|
|
728
|
+
if (!(xg.outputs || []).includes(native)) continue;
|
|
729
|
+
for (const sym of xg.inputs || []) {
|
|
730
|
+
const a = assets[sym];
|
|
731
|
+
if (!a?.enabled || !a?.input || !(a.chains || []).includes(from)) continue;
|
|
732
|
+
const via = bridgeStableFor(caps, sym, from, to);
|
|
733
|
+
if (!via) continue;
|
|
734
|
+
out.push({ input: sym, fromChain: from, output: native, toChain: to, flow: "xchain_gas", via });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return out;
|
|
738
|
+
}
|
|
739
|
+
var isEvmAddress = (v) => /^0x[0-9a-fA-F]{40}$/.test(v.trim());
|
|
740
|
+
|
|
741
|
+
// ../../mcp/worker/routes.ts
|
|
742
|
+
import { z } from "zod";
|
|
743
|
+
var CHAIN = z.enum(["base", "solana", "radius"]);
|
|
744
|
+
var ROUTE = {
|
|
745
|
+
inSym: z.string().min(1).max(16),
|
|
746
|
+
fromChain: CHAIN,
|
|
747
|
+
outSym: z.string().min(1).max(16),
|
|
748
|
+
toChain: CHAIN,
|
|
749
|
+
uiAmount: z.number().positive()
|
|
750
|
+
};
|
|
751
|
+
var same = (a, b) => a.toUpperCase() === b.toUpperCase();
|
|
752
|
+
async function resolveEngine(ui, r) {
|
|
753
|
+
const caps = await ui.get("/api/capabilities");
|
|
754
|
+
const pairs = [...Array.isArray(caps.pairs) ? caps.pairs : [], ...gasPairsFrom(caps), ...xchainGasPairsFrom(caps)];
|
|
755
|
+
const pair = pairs.find((p) => same(p.input, r.inSym) && p.fromChain === r.fromChain && same(p.output, r.outSym) && p.toChain === r.toChain);
|
|
756
|
+
const engine = pair ? engineFor(pair) : null;
|
|
757
|
+
if (!pair || !engine) throw new Error(`route not enabled: ${r.inSym}@${r.fromChain} -> ${r.outSym}@${r.toChain}`);
|
|
758
|
+
return { engine, pair };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ../../mcp/worker/build.ts
|
|
762
|
+
var RFQ_TTL_S = 1800;
|
|
763
|
+
var RAIL = {
|
|
764
|
+
intent: "evm-origin",
|
|
765
|
+
solOrigin: "sol-origin",
|
|
766
|
+
baseToRadius: "base->radius",
|
|
767
|
+
radiusToBase: "radius->base",
|
|
768
|
+
radiusToSolana: "radius->solana",
|
|
769
|
+
solanaToRadius: "solana->radius"
|
|
770
|
+
};
|
|
771
|
+
var eqAddr = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
772
|
+
function jsonSafe(v) {
|
|
773
|
+
return JSON.parse(JSON.stringify(v, (_k, x) => typeof x === "bigint" ? x.toString() : x));
|
|
774
|
+
}
|
|
775
|
+
function orderJson(o) {
|
|
776
|
+
return jsonSafe({ user: o.user, inputToken: o.inputToken, inputAmount: o.inputAmount, outputToken: o.outputToken, minOutputAmount: o.minOutputAmount, solver: o.solver, deadline: o.deadline, nonce: o.nonce });
|
|
777
|
+
}
|
|
778
|
+
function registryToken(net, sym, chain) {
|
|
779
|
+
const tokens = REGISTRY[net]?.tokens ?? {};
|
|
780
|
+
const key = Object.keys(tokens).find((k) => k.toUpperCase() === sym.toUpperCase());
|
|
781
|
+
const t = key ? tokens[key]?.[chain] : void 0;
|
|
782
|
+
if (!t?.address) throw new Error(`${sym} has no ${chain} token in the ${net} registry`);
|
|
783
|
+
return t;
|
|
784
|
+
}
|
|
785
|
+
function evmUser(u) {
|
|
786
|
+
try {
|
|
787
|
+
return getAddress5(u);
|
|
788
|
+
} catch {
|
|
789
|
+
throw new Error("user must be a valid EVM (0x\u2026) address for this route");
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function solUser(u) {
|
|
793
|
+
if (!isValidSolAddress(u)) throw new Error("user must be a valid Solana (base58) address for this route");
|
|
794
|
+
return u;
|
|
795
|
+
}
|
|
796
|
+
function permit(domain, token2, owner, spender, value, nonce, deadline) {
|
|
797
|
+
return jsonSafe({
|
|
798
|
+
domain: { name: domain.name, version: domain.version, chainId: domain.chainId, verifyingContract: token2 },
|
|
799
|
+
types: PERMIT_TYPES,
|
|
800
|
+
primaryType: "Permit",
|
|
801
|
+
message: { owner, spender, value, nonce, deadline }
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function redeemInput(info, sym) {
|
|
805
|
+
const ri = info.redeemInputs || {};
|
|
806
|
+
return ri[sym] || ri[sym.toUpperCase()] || ri[sym === "cfUSD" ? "CFUSD" : sym];
|
|
807
|
+
}
|
|
808
|
+
async function baseInfo(d) {
|
|
809
|
+
const info = await d.ui.get("/api/base/info");
|
|
810
|
+
if (!eqAddr(String(info.router), loadManifest()[d.net].base.rfqRouter)) throw new Error("refused: /api/base/info router is not the RfqSwap in the address manifest");
|
|
811
|
+
return info;
|
|
812
|
+
}
|
|
813
|
+
async function baseRfq(d, a, engine) {
|
|
814
|
+
const user = evmUser(a.user);
|
|
815
|
+
const info = await baseInfo(d);
|
|
816
|
+
const router = getAddress5(info.router);
|
|
817
|
+
const amount = String(a.uiAmount);
|
|
818
|
+
let inToken, inDec, outToken, outDec, minOut, domain;
|
|
819
|
+
if (engine === "baseRedeem") {
|
|
820
|
+
const ri = redeemInput(info, a.inSym);
|
|
821
|
+
if (!ri?.addr || !ri.permitDomain) throw new Error(ri?.permitError || `${a.inSym} is not a configured Base redeem input`);
|
|
822
|
+
const q = await d.ui.get("/api/base/redeem/quote", { in: a.inSym, amount });
|
|
823
|
+
if (!q.minOutBase || !q.usdc) throw new Error("redeem quote is missing the signed floor; refusing to build");
|
|
824
|
+
[inToken, inDec, outToken, outDec, minOut, domain] = [getAddress5(ri.addr), Number(ri.dec), getAddress5(q.usdc), Number(q.usdcDec ?? 6), BigInt(q.minOutBase), ri.permitDomain];
|
|
825
|
+
} else {
|
|
826
|
+
const out = info.out?.[a.outSym];
|
|
827
|
+
if (!out?.addr) throw new Error(`${a.outSym} is not a configured Base output`);
|
|
828
|
+
if (a.uiAmount > Number(out.inventory ?? 0)) throw new Error(`solver ${a.outSym} inventory is ${Number(out.inventory ?? 0).toFixed(2)}; lower the amount`);
|
|
829
|
+
if (engine === "baseBuy") {
|
|
830
|
+
[inToken, inDec, domain] = [getAddress5(info.usdc), Number(info.usdcDec), { name: info.usdcName, version: info.usdcVersion, chainId: Number(info.chainId) }];
|
|
831
|
+
} else {
|
|
832
|
+
const ri = redeemInput(info, a.inSym);
|
|
833
|
+
if (!ri?.addr || !ri.permitDomain) throw new Error(ri?.permitError || `${a.inSym} is not a configured Base input`);
|
|
834
|
+
[inToken, inDec, domain] = [getAddress5(ri.addr), Number(ri.dec), ri.permitDomain];
|
|
835
|
+
}
|
|
836
|
+
[outToken, outDec] = [getAddress5(out.addr), Number(out.dec)];
|
|
837
|
+
minOut = parseUnits(amount, outDec);
|
|
838
|
+
}
|
|
839
|
+
const inputAmount = parseUnits(amount, inDec);
|
|
840
|
+
const deadline = BigInt(Math.floor((d.now?.() ?? Date.now()) / 1e3) + RFQ_TTL_S);
|
|
841
|
+
const built = buildRfqOrder({
|
|
842
|
+
user,
|
|
843
|
+
inputToken: inToken,
|
|
844
|
+
outputToken: outToken,
|
|
845
|
+
solver: getAddress5(info.solver),
|
|
846
|
+
router,
|
|
847
|
+
chainId: Number(info.chainId),
|
|
848
|
+
inputAmount,
|
|
849
|
+
minOutputAmount: minOut,
|
|
850
|
+
nonce: await d.chain.rfqNonce(router, user),
|
|
851
|
+
deadline,
|
|
852
|
+
inputSymbol: a.inSym,
|
|
853
|
+
outputSymbol: a.outSym,
|
|
854
|
+
inputDecimals: inDec,
|
|
855
|
+
outputDecimals: outDec
|
|
856
|
+
});
|
|
857
|
+
const order = orderJson(built.order);
|
|
858
|
+
const permitTypedData = permit(domain, inToken, user, router, inputAmount, await d.chain.permitNonce(inToken, user), deadline);
|
|
859
|
+
const route = engine === "baseBuy" ? "base-buy" : engine === "basePar" ? "base-par" : "base-redeem";
|
|
860
|
+
const submitBody = engine === "baseRedeem" ? { user, in: a.inSym, uiAmount: a.uiAmount } : { user, uiAmount: a.uiAmount, out: a.outSym, ...engine === "basePar" ? { in: a.inSym } : {} };
|
|
861
|
+
return {
|
|
862
|
+
kind: "evm-rfq",
|
|
863
|
+
chain: "base",
|
|
864
|
+
route,
|
|
865
|
+
net: d.net,
|
|
866
|
+
inSym: a.inSym,
|
|
867
|
+
outSym: a.outSym,
|
|
868
|
+
uiAmount: a.uiAmount,
|
|
869
|
+
user,
|
|
870
|
+
recipient: user,
|
|
871
|
+
orderTypedData: jsonSafe(built.typedData),
|
|
872
|
+
permitTypedData,
|
|
873
|
+
order,
|
|
874
|
+
submitPath: engine === "baseRedeem" ? "/api/base/redeem" : "/api/base/swap",
|
|
875
|
+
submitBody
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
async function baseGas(d, a) {
|
|
879
|
+
const user = evmUser(a.user);
|
|
880
|
+
const amount = String(a.uiAmount);
|
|
881
|
+
const quote = await d.ui.get("/api/gas/quote", { chain: "base", in: a.inSym, amount });
|
|
882
|
+
if (quote.fillable === false) throw new Error(quote.reason || "gas top-up is not fillable right now");
|
|
883
|
+
const info = await baseInfo(d);
|
|
884
|
+
const ri = redeemInput(info, a.inSym);
|
|
885
|
+
const isUsdc = a.inSym.toUpperCase() === "USDC";
|
|
886
|
+
const token2 = ri?.addr ? getAddress5(ri.addr) : isUsdc ? getAddress5(info.usdc) : null;
|
|
887
|
+
const domain = ri?.addr ? ri.permitDomain : isUsdc ? { name: info.usdcName, version: info.usdcVersion, chainId: Number(info.chainId) } : null;
|
|
888
|
+
if (!token2 || !domain?.name) throw new Error(ri?.permitError || `${a.inSym} permit domain unavailable; cannot sign safely`);
|
|
889
|
+
const dec = Number(ri?.dec ?? info.usdcDec);
|
|
890
|
+
if (quote.inDecimals != null && dec !== Number(quote.inDecimals)) throw new Error(`${a.inSym} decimals disagree (quote ${quote.inDecimals}, token info ${dec}); refusing to build`);
|
|
891
|
+
const value = parseUnits(amount, dec);
|
|
892
|
+
const deadline = BigInt(Math.floor((d.now?.() ?? Date.now()) / 1e3) + RFQ_TTL_S);
|
|
893
|
+
const spender = getAddress5(info.solver);
|
|
894
|
+
return {
|
|
895
|
+
kind: "evm-permit",
|
|
896
|
+
chain: "base",
|
|
897
|
+
route: "gas-base",
|
|
898
|
+
net: d.net,
|
|
899
|
+
inSym: a.inSym,
|
|
900
|
+
outSym: a.outSym,
|
|
901
|
+
uiAmount: a.uiAmount,
|
|
902
|
+
user,
|
|
903
|
+
recipient: user,
|
|
904
|
+
permitTypedData: permit(domain, token2, user, spender, value, await d.chain.permitNonce(token2, user), deadline),
|
|
905
|
+
submitPath: "/api/gas/buy",
|
|
906
|
+
submitBody: { user, in: a.inSym, amount, deadline: Number(deadline), quotedOut: quote.outBaseUnits, quotedAt: d.now?.() ?? Date.now() }
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
async function solSame(d, a, engine) {
|
|
910
|
+
const user = solUser(a.user);
|
|
911
|
+
const base2 = { kind: "sol-tx", net: d.net, inSym: a.inSym, outSym: a.outSym, uiAmount: a.uiAmount, user, recipient: user };
|
|
912
|
+
if (engine === "gasBuy") {
|
|
913
|
+
const amount = String(a.uiAmount);
|
|
914
|
+
const quote = await d.ui.get("/api/gas/quote", { chain: "solana", in: a.inSym, amount });
|
|
915
|
+
if (quote.fillable === false) throw new Error(quote.reason || "gas top-up is not fillable right now");
|
|
916
|
+
const b2 = await d.ui.post("/api/gas/build", { user, in: a.inSym, amount, quotedOut: quote.outBaseUnits, quotedAt: d.now?.() ?? Date.now() });
|
|
917
|
+
return { ...base2, route: "gas-sol", txBase64: b2.txB64, versioned: false, topupId: b2.topupId };
|
|
918
|
+
}
|
|
919
|
+
if (engine === "solRedeem") {
|
|
920
|
+
const b2 = await d.ui.post("/api/sol/redeem", { net: d.net, user, in: a.inSym, uiAmount: a.uiAmount });
|
|
921
|
+
return { ...base2, route: "sol-redeem", txBase64: b2.txB64, versioned: !!b2.versioned };
|
|
922
|
+
}
|
|
923
|
+
const body = engine === "solBuy" ? { net: d.net, user, uiAmount: a.uiAmount, out: SOL_BUY_VAL[a.outSym] || "SBC" } : { net: d.net, user, uiAmount: a.uiAmount, in: a.inSym, out: a.outSym };
|
|
924
|
+
const b = await d.ui.post("/api/sol/swap", body);
|
|
925
|
+
return { ...base2, route: engine === "solBuy" ? "sol-buy" : "sol-par", txBase64: b.txB64, versioned: !!b.versioned };
|
|
926
|
+
}
|
|
927
|
+
async function evmOrigin(d, a, engine) {
|
|
928
|
+
const user = evmUser(a.user);
|
|
929
|
+
const recipient = a.recipient;
|
|
930
|
+
const src = a.fromChain;
|
|
931
|
+
const dst = a.toChain;
|
|
932
|
+
const body = engine === "intent" ? { user, inSym: a.inSym, outSym: a.outSym, uiAmount: a.uiAmount, recipientSol: recipient } : dst === "solana" ? { user, src, dst, inSym: a.inSym, outSym: a.outSym, uiAmount: a.uiAmount, recipientSol: recipient } : { user, src, dst, inSym: a.inSym, outSym: a.outSym, uiAmount: a.uiAmount, recipient: getAddress5(recipient) };
|
|
933
|
+
const open = await d.ui.post("/api/xchain/evm-origin/open/build", body);
|
|
934
|
+
const rail = RAIL[engine];
|
|
935
|
+
if (engine !== "intent" && open.corridor !== rail) throw new Error("refused: the server built this order for a different route");
|
|
936
|
+
const m = loadManifest()[d.net];
|
|
937
|
+
const expectEscrow = src === "radius" ? m.radius?.escrow : m.base.escrow;
|
|
938
|
+
if (!expectEscrow || !eqAddr(String(open.escrow), expectEscrow)) throw new Error(`refused: escrow ${open.escrow} is not the ${src} SourceEscrow in the address manifest`);
|
|
939
|
+
const escrow = getAddress5(open.escrow);
|
|
940
|
+
const inAmt = BigInt(open.inputAmount);
|
|
941
|
+
const tok = registryToken(d.net, a.inSym, src);
|
|
942
|
+
if (!eqAddr(String(open.inputToken), tok.address)) throw new Error(`refused: input token ${open.inputToken} is not ${a.inSym} on ${src} per the registry`);
|
|
943
|
+
if (inAmt !== parseUnits(String(a.uiAmount), tok.decimals)) throw new Error(`refused: input amount ${open.inputAmount} does not equal ${a.uiAmount} ${a.inSym}`);
|
|
944
|
+
const order = rehydrateOrder(open.order);
|
|
945
|
+
if (!eqAddr(order.swapper, user)) throw new Error("refused: order swapper is not the user");
|
|
946
|
+
if (!eqAddr(order.settlementContract, escrow)) throw new Error("refused: order settlementContract is not the escrow");
|
|
947
|
+
const chainId = src === "radius" ? Number(open.chainId) : Number((await d.ui.get("/api/config")).chainId);
|
|
948
|
+
return {
|
|
949
|
+
kind: "evm-txs",
|
|
950
|
+
chain: src,
|
|
951
|
+
route: "xchain-evm-origin",
|
|
952
|
+
chainId,
|
|
953
|
+
net: d.net,
|
|
954
|
+
inSym: a.inSym,
|
|
955
|
+
outSym: a.outSym,
|
|
956
|
+
uiAmount: a.uiAmount,
|
|
957
|
+
user,
|
|
958
|
+
recipient,
|
|
959
|
+
orderId: open.orderId,
|
|
960
|
+
rail,
|
|
961
|
+
legacyGas: src === "radius",
|
|
962
|
+
txs: [
|
|
963
|
+
{ to: getAddress5(open.inputToken), data: encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [escrow, inAmt] }), value: "0" },
|
|
964
|
+
{ to: escrow, data: encodeFunctionData({ abi: EVM_OPEN_ABI, functionName: "open", args: [order, "0x", open.fillerData] }), value: "0" }
|
|
965
|
+
]
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
async function solOrigin(d, a, engine) {
|
|
969
|
+
const user = solUser(a.user);
|
|
970
|
+
const recipient = getAddress5(a.recipient);
|
|
971
|
+
const toRadius = engine === "solanaToRadius";
|
|
972
|
+
const open = await d.ui.post("/api/xchain/sol-origin/open/build", { user, inSym: a.inSym, outSym: a.outSym, uiAmount: a.uiAmount, recipientEvm: recipient, ...toRadius ? { dst: "radius" } : {} });
|
|
973
|
+
if (toRadius && open.corridor !== "solana->radius") throw new Error("refused: the server built this order for a different route");
|
|
974
|
+
return {
|
|
975
|
+
kind: "sol-tx",
|
|
976
|
+
route: "xchain-sol-origin",
|
|
977
|
+
net: d.net,
|
|
978
|
+
inSym: a.inSym,
|
|
979
|
+
outSym: a.outSym,
|
|
980
|
+
uiAmount: a.uiAmount,
|
|
981
|
+
user,
|
|
982
|
+
recipient,
|
|
983
|
+
orderId: open.orderId,
|
|
984
|
+
rail: RAIL[engine],
|
|
985
|
+
txBase64: open.txB64,
|
|
986
|
+
versioned: false
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
async function buildSignRequest(d, a) {
|
|
990
|
+
const { engine } = await resolveEngine(d.ui, a);
|
|
991
|
+
const xchain = a.fromChain !== a.toChain;
|
|
992
|
+
if (engine === "xchainGas") throw new Error(`cross-chain gas runs as two swaps: a stable ${a.fromChain}->${a.toChain}, then gas on ${a.toChain}`);
|
|
993
|
+
if (xchain && !xchainRecipientValid(engine, a.recipient ?? "")) throw new Error(`a valid ${a.toChain} recipient address is required for this route`);
|
|
994
|
+
switch (engine) {
|
|
995
|
+
case "baseBuy":
|
|
996
|
+
case "basePar":
|
|
997
|
+
case "baseRedeem":
|
|
998
|
+
return baseRfq(d, a, engine);
|
|
999
|
+
case "solBuy":
|
|
1000
|
+
case "solPar":
|
|
1001
|
+
case "solRedeem":
|
|
1002
|
+
return solSame(d, a, engine);
|
|
1003
|
+
case "gasBuy":
|
|
1004
|
+
return a.fromChain === "base" ? baseGas(d, a) : solSame(d, a, "gasBuy");
|
|
1005
|
+
case "intent":
|
|
1006
|
+
case "baseToRadius":
|
|
1007
|
+
case "radiusToBase":
|
|
1008
|
+
case "radiusToSolana":
|
|
1009
|
+
return evmOrigin(d, a, engine);
|
|
1010
|
+
case "solOrigin":
|
|
1011
|
+
case "solanaToRadius":
|
|
1012
|
+
return solOrigin(d, a, engine);
|
|
1013
|
+
}
|
|
1014
|
+
throw new Error(`route engine ${engine} is not supported over MCP`);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// ../../mcp/worker/order-rail.ts
|
|
1018
|
+
import { z as z2 } from "zod";
|
|
1019
|
+
var RAIL2 = z2.enum(["sol-origin", "evm-origin", "base->radius", "radius->base", "radius->solana", "solana->radius"]);
|
|
1020
|
+
async function findOrderOnRails(lookup) {
|
|
1021
|
+
let first;
|
|
1022
|
+
const failed = [];
|
|
1023
|
+
for (const r of RAIL2.options) {
|
|
1024
|
+
let res;
|
|
1025
|
+
try {
|
|
1026
|
+
res = await lookup(r);
|
|
1027
|
+
} catch (e) {
|
|
1028
|
+
if (/invalid orderId/.test(e.message)) throw e;
|
|
1029
|
+
failed.push(`${r}: ${e.message}`);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
if (res?.status !== "unknown") return res;
|
|
1033
|
+
first ??= res;
|
|
1034
|
+
}
|
|
1035
|
+
if (failed.length) throw new Error(`not found on readable rails; unreadable: ${failed.join(" ; ")}`);
|
|
1036
|
+
return first;
|
|
1037
|
+
}
|
|
1038
|
+
export {
|
|
1039
|
+
COMPUTE_BUDGET,
|
|
1040
|
+
DEFAULT_LIMITS,
|
|
1041
|
+
GAS_FLOOR_PCT,
|
|
1042
|
+
RAIL2 as RAIL,
|
|
1043
|
+
Refuse,
|
|
1044
|
+
buildSignRequest,
|
|
1045
|
+
checkIntent,
|
|
1046
|
+
decodeEvmRequest,
|
|
1047
|
+
decodeSolRequest,
|
|
1048
|
+
findOrderOnRails,
|
|
1049
|
+
gasFloorOf,
|
|
1050
|
+
gasQuoteQuery,
|
|
1051
|
+
gasSpend,
|
|
1052
|
+
hydrateTypedData,
|
|
1053
|
+
isGasRoute,
|
|
1054
|
+
loadManifest,
|
|
1055
|
+
need,
|
|
1056
|
+
token
|
|
1057
|
+
};
|