@anywalletkit/protocol-swap2-core 0.8.0 → 0.10.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 +2 -2
- package/dist/{chunk-FHEU7E5A.js → chunk-AY7DVYAN.js} +3 -3
- package/dist/{chunk-5FPDKWBG.js → chunk-F5TRF52T.js} +12 -3
- package/dist/chunk-F5TRF52T.js.map +1 -0
- package/dist/{chunk-6MTZNYKM.js → chunk-KB2GX6R2.js} +2 -1
- package/dist/{chunk-6MTZNYKM.js.map → chunk-KB2GX6R2.js.map} +1 -1
- package/dist/{chunk-CRCDWV3L.js → chunk-PN76KVPT.js} +59 -12
- package/dist/chunk-PN76KVPT.js.map +1 -0
- package/dist/index.cjs +67 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -4
- package/dist/remote.cjs +1 -0
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.js +1 -1
- package/dist/server.cjs +13 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/dist/swap2/base.cjs +58 -11
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.d.cts +18 -0
- package/dist/swap2/base.d.ts +18 -0
- package/dist/swap2/base.js +2 -1
- package/dist/swap2/single.cjs +119 -68
- package/dist/swap2/single.cjs.map +1 -1
- package/dist/swap2/single.js +3 -3
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +18 -0
- package/dist/types.d.ts +18 -0
- package/dist/validate.cjs +11 -2
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +13 -3
- package/dist/validate.d.ts +13 -3
- package/dist/validate.js +5 -1
- package/package.json +6 -6
- package/dist/chunk-5FPDKWBG.js.map +0 -1
- package/dist/chunk-CRCDWV3L.js.map +0 -1
- /package/dist/{chunk-FHEU7E5A.js.map → chunk-AY7DVYAN.js.map} +0 -0
package/README.md
CHANGED
|
@@ -15,9 +15,9 @@ On npm: [@anywalletkit/protocol-swap2-core](https://www.npmjs.com/package/@anywa
|
|
|
15
15
|
|
|
16
16
|
- **`BaseSwap2Protocol` / `SingleProviderSwap2`** — a `Swap2Protocol` (from `@anywalletkit/core`) over a provider. It quotes, binds the execution to the quote, runs approvals, sends, and resumes status by id (`getSwap2Status`, `claimSwap2` for two-phase routes).
|
|
17
17
|
- **Safety checks.** Quote validation, a minimum-output floor, fee caps, a review gate before signing, and refusals that name their reason. Status ids look like `awk1.<provider>.<intent>[.<hash>]` (`SWAP2_ID_PREFIX`).
|
|
18
|
-
- **`remoteProvider`** — runs a provider on your server (API keys, shared state) while the app keeps the keys and signs. The backend is not the authority over what the wallet signs: spread the provider package's pins into the stub (`remoteProvider({name, baseUrl, ...lifiPins})`, `fyndPins`, `uniswapPins`, `cctpPins`…). The engine then lets an EVM route call only the pinned `callTargets`, approve only the pinned `spenders`, and runs the provider's own `verifyQuote` (Fynd: `checkFyndTransaction`) on the client; a stub without pins gets no EVM calldata route at all (the router drops it with the reason). A route that pays an address the backend names — a deposit address, or a plain value send — runs only on a stub the app opened with `depositAddressRoutes: true` (`nearIntentsPins`; `thorchainPins()` and `chainflipPins` for their Tron vaults), on every chain. Every token call in a swap's calldata, from any provider, must move the source token, at most the swap amount; operator and Permit2 approvals are never a swap's main call. A typed (gasless) order is signed only when it is the swap's own Limit Order Protocol `Order` — verifying contract = the approved settlement contract, maker = this account, the source token, exactly the input — never a `Permit*` or another typed message; a provider's own check of the build can run on the client too (`verifyBuild`), and a remote stub's typed order is signed only when it has one (Omniston: `omnistonVerifyBuild` opens the order with the `orderExtension` the build carries). A TON cell-body deposit (`bodyBoc`) is bound too: it attaches the input plus at most `tonGasCeiling` of gas (default 1 TON; a jetton source: the ceiling alone), a jetton source's body must be a TEP-74 transfer of exactly the input from this account's own jetton wallet (`getJettonWalletAddress`) with the excess returning here, the decoded body reaches the review (`cellBodyDecoded`), and a remote stub's cell body needs the app's own validator (`verifyBuild`). An absolute `baseUrl` must be `https:` (a same-origin path is fine; `allowInsecureBaseUrl` for local development). A provider with a claim step (CCTP) also takes `claimTargets` from the app: `claimSwap2` calls only those contracts.
|
|
18
|
+
- **`remoteProvider`** — runs a provider on your server (API keys, shared state) while the app keeps the keys and signs. The backend is not the authority over what the wallet signs: spread the provider package's pins into the stub (`remoteProvider({name, baseUrl, ...lifiPins})`, `fyndPins`, `uniswapPins`, `cctpPins`…). The engine then lets an EVM route call only the pinned `callTargets`, approve only the pinned `spenders`, and runs the provider's own `verifyQuote` (Fynd: `checkFyndTransaction`) on the client; a stub without pins gets no EVM calldata route at all (the router drops it with the reason), and neither does a stub whose pins name call targets but carry no check of the call itself — `verifyQuote`, or a `verifyBuild` the app supplies — since a pinned contract executes whatever its arguments say. Every shipped EVM pins export carries one. A route that pays an address the backend names — a deposit address, or a plain value send — runs only on a stub the app opened with `depositAddressRoutes: true` (`nearIntentsPins`; `thorchainPins()` and `chainflipPins` for their Tron vaults), on every chain. Every token call in a swap's calldata, from any provider, must move the source token, at most the swap amount; operator and Permit2 approvals are never a swap's main call. A typed (gasless) order is signed only when it is the swap's own Limit Order Protocol `Order` — verifying contract = the approved settlement contract, maker = this account, the source token, exactly the input — never a `Permit*` or another typed message; a provider's own check of the build can run on the client too (`verifyBuild`), and a remote stub's typed order is signed only when it has one (Omniston: `omnistonVerifyBuild` opens the order with the `orderExtension` the build carries). A TON cell-body deposit (`bodyBoc`) is bound too: it attaches the input plus at most `tonGasCeiling` of gas (default 1 TON; a jetton source: the ceiling alone), a jetton source's body must be a TEP-74 transfer of exactly the input from this account's own jetton wallet (`getJettonWalletAddress`) with the excess returning here, the decoded body reaches the review (`cellBodyDecoded`), and a remote stub's cell body needs the app's own validator (`verifyBuild`). An absolute `baseUrl` must be `https:` (a same-origin path is fine; `allowInsecureBaseUrl` for local development). A provider with a claim step (CCTP) also takes `claimTargets` from the app: `claimSwap2` calls only those contracts.
|
|
19
19
|
- **`createSwap2Handler`** (`@anywalletkit/protocol-swap2-core/server`, server-only) — the other end of `remoteProvider`: validates every request, HMAC-seals only quotes that pass the engine's own checks (`validQuote`: provider, input, floor, slippage, expiry; else 502) and builds only sealed, unexpired ones, puts an intent token on every swap id (tracking, claims and notices without it are refused), passes refusals through with URLs redacted; you supply `guard` (auth + rate limits, it sees the validated request) and, with per-user sign-in, `subject` (quotes and swap ids bound to the user). A reference server: [`examples/server`](https://github.com/ultratestbro/anywalletkit/tree/main/examples/server).
|
|
20
|
-
- **Tokens outside the catalog** — `allowUnlistedTokens: true` on the engine (and on `createSwap2Handler` for a remote engine) lets a swap name a token the catalog does not list by its EVM contract or Solana mint: the user's own tokens. Off by default. The engine reads the token's decimals with the account's `getTokenInfo` and sends them as `sourceDecimals`; providers that trade any token resolve the source with `sourceAssetOf(params)`, the rest refuse it. Approvals and value binding are the same as for catalog tokens.
|
|
20
|
+
- **Tokens outside the catalog** — `allowUnlistedTokens: true` on the engine (and on `createSwap2Handler` for a remote engine) lets a swap name a token the catalog does not list by its EVM contract or Solana mint: the user's own tokens. Off by default. The engine reads the token's decimals with the account's `getTokenInfo` and sends them as `sourceDecimals`; providers that trade any token resolve the source with `sourceAssetOf(params)`, the rest refuse it. Approvals and value binding are the same as for catalog tokens. A bridge to an unlisted destination token takes its decimals from the caller's `toTokenDecimals` (read on the destination chain), never from the quote alone: a quote whose `destDecimals` disagree is refused, and without them a Mayan leg out of Solana has no reviewed minimum and is refused at signing.
|
|
21
21
|
- **Errors** — `Swap2Error` and its subclasses (quote, validation, execution, fee limit, status…).
|
|
22
22
|
- **Activity helpers** — what `describeActivity` implementations share.
|
|
23
23
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BaseSwap2Protocol
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PN76KVPT.js";
|
|
4
4
|
import {
|
|
5
5
|
remotePinRefusal,
|
|
6
6
|
validQuote
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-F5TRF52T.js";
|
|
8
8
|
|
|
9
9
|
// src/swap2/single.ts
|
|
10
10
|
var SingleProviderSwap2 = class extends BaseSwap2Protocol {
|
|
@@ -68,4 +68,4 @@ var SingleProviderSwap2 = class extends BaseSwap2Protocol {
|
|
|
68
68
|
export {
|
|
69
69
|
SingleProviderSwap2
|
|
70
70
|
};
|
|
71
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-AY7DVYAN.js.map
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
// src/validate.ts
|
|
2
|
+
var SLIPPAGE_ROUNDING_BPS = 1;
|
|
3
|
+
function slippageFloor(dest, slippageBps) {
|
|
4
|
+
const keep = 1e4 - slippageBps - SLIPPAGE_ROUNDING_BPS;
|
|
5
|
+
return keep <= 0 ? 0n : dest * BigInt(keep) / 10000n;
|
|
6
|
+
}
|
|
2
7
|
function validQuote(q, params, provider) {
|
|
3
8
|
try {
|
|
4
9
|
if (q.provider !== provider || q.sourceAmount !== params.amountIn) return false;
|
|
@@ -13,8 +18,7 @@ function validQuote(q, params, provider) {
|
|
|
13
18
|
}
|
|
14
19
|
if (params.slippageBps !== void 0) {
|
|
15
20
|
if (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 1e4) return false;
|
|
16
|
-
|
|
17
|
-
if (min < required) return false;
|
|
21
|
+
if (min < slippageFloor(dest, params.slippageBps)) return false;
|
|
18
22
|
}
|
|
19
23
|
if (!Number.isFinite(q.etaSeconds) || q.etaSeconds < 0) return false;
|
|
20
24
|
if (!Number.isFinite(q.reliability) || q.reliability < 0 || q.reliability > 1) return false;
|
|
@@ -52,11 +56,16 @@ function remotePinRefusal(provider, q, chain) {
|
|
|
52
56
|
if (tx.data !== void 0 && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {
|
|
53
57
|
return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;
|
|
54
58
|
}
|
|
59
|
+
if (tx.data !== void 0 && provider.verifiesQuotes !== true && provider.verifiesBuilds !== true) {
|
|
60
|
+
return `${provider.name}: the route's calldata is not checked by the app (remoteProvider verifyQuote / verifyBuild) \u2014 a pinned target alone does not say whom it pays`;
|
|
61
|
+
}
|
|
55
62
|
return null;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export {
|
|
66
|
+
SLIPPAGE_ROUNDING_BPS,
|
|
67
|
+
slippageFloor,
|
|
59
68
|
validQuote,
|
|
60
69
|
remotePinRefusal
|
|
61
70
|
};
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-F5TRF52T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validate.ts"],"sourcesContent":["import type {CrossChainProvider, Quote, QuoteParams} from \"./types.js\";\n\n/**\n * How far past the requested slippage a quote's minimum may sit and still count as \"at\" it. Providers derive the\n * minimum with their own rounding: LI.FI's Mayan answers land a few base units under an exact floor on 18-decimal\n * outputs (50.0000000001 bps at a requested 50, live 2026-11-02), which refused half of them. A minimum that is more\n * than this past the request is still refused.\n */\nexport const SLIPPAGE_ROUNDING_BPS = 1;\n\n/** The lowest minimum a quote of `dest` may carry at `slippageBps`: floor(dest × (10 000 − slippageBps − {@link SLIPPAGE_ROUNDING_BPS}) / 10 000). */\nexport function slippageFloor(dest: bigint, slippageBps: number): bigint {\n const keep = 10_000 - slippageBps - SLIPPAGE_ROUNDING_BPS;\n return keep <= 0 ? 0n : (dest * BigInt(keep)) / 10_000n;\n}\n\n/** Shared quote validator used by both the aggregate and single-provider paths. */\nexport function validQuote(q: Quote, params: QuoteParams, provider: string): boolean {\n try {\n if (q.provider !== provider || q.sourceAmount !== params.amountIn) return false;\n if (typeof q.intentId !== \"string\" || !q.intentId || typeof q.depositAddress !== \"string\" || (q.depositMemo !== null && typeof q.depositMemo !== \"string\")) return false;\n if (![q.sourceAmount, q.destAmount, q.minDestAmount].every((value) => /^[0-9]+$/.test(value))) return false;\n const source = BigInt(q.sourceAmount);\n const dest = BigInt(q.destAmount);\n const min = BigInt(q.minDestAmount);\n if (source <= 0n || dest <= 0n || min <= 0n || min > dest) return false;\n if (params.minAmountOut !== undefined) {\n if (!/^[0-9]+$/.test(params.minAmountOut) || BigInt(params.minAmountOut) <= 0n || min < BigInt(params.minAmountOut)) return false;\n }\n if (params.slippageBps !== undefined) {\n if (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 10_000) return false;\n // Providers round the floor DOWN (1Click, LI.FI: floor(dest × (1 − s))), some through\n // floats. A ceil here refused nearly every slippage-bounded quote by one base unit.\n if (min < slippageFloor(dest, params.slippageBps)) return false;\n }\n if (!Number.isFinite(q.etaSeconds) || q.etaSeconds < 0) return false;\n if (!Number.isFinite(q.reliability) || q.reliability < 0 || q.reliability > 1) return false;\n if (!Number.isFinite(q.feeUsd) || q.feeUsd < 0) return false;\n if (q.fees && (!Array.isArray(q.fees) || !q.fees.every((fee) =>\n [\"network\", \"protocol\", \"affiliate\", \"other\"].includes(fee.type) &&\n /^[0-9]+$/.test(fee.amount) &&\n typeof fee.token === \"string\" && fee.token.length > 0 &&\n typeof fee.included === \"boolean\" &&\n (fee.description === undefined || typeof fee.description === \"string\")\n ))) return false;\n if (q.providerData && (Array.isArray(q.providerData) || typeof q.providerData !== \"object\" || !Object.values(q.providerData).every((value) => typeof value === \"string\"))) return false;\n const tx = q.txRequest;\n if (tx) {\n if (tx.value !== undefined && !/^[0-9]+$/.test(tx.value)) return false;\n if (tx.gasLimit !== undefined && !/^[0-9]+$/.test(tx.gasLimit)) return false;\n if (tx.chainId !== undefined && (!Number.isSafeInteger(tx.chainId) || tx.chainId <= 0)) return false;\n if ([tx.to, tx.data, tx.approvalAddress, tx.serializedTx].some((value) => value !== undefined && typeof value !== \"string\")) return false;\n }\n // Execution is always re-quoted. An unknown/zero expiry cannot prove that\n // the transaction is still safe to sign, so contribution packages reject it.\n if (!Number.isFinite(q.validUntilMs) || q.validUntilMs <= Date.now()) return false;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Why a remote provider's quote cannot execute in this app, or null: its EVM transaction calls or\n * approves an address the app did not pin (`remoteProvider` `callTargets` / `spenders`), or it pays\n * an address the backend named — a deposit address or a plain value send — on a stub the app did not\n * open to such routes (`depositAddressRoutes`, scan 2026-10-28 F9), or it is EVM calldata on a stub without the\n * app's own check of what that calldata does (`verifyQuote` / `verifyBuild`, scan 2026-10-30 F3). The router and the\n * single-provider path drop such a quote with this reason, so the auction never picks a route\n * execution would refuse (scan 2026-10-27 modules, F8/F9/F16); execution checks the build again, in\n * full. Local providers are not judged here.\n */\nexport function remotePinRefusal(provider: Pick<CrossChainProvider, \"name\" | \"remote\" | \"callTargets\" | \"spenders\" | \"depositAddressRoutes\" | \"verifiesQuotes\" | \"verifiesBuilds\">, q: Quote, chain: string): string | null {\n if (provider.remote !== true) return null;\n const tx = q.txRequest;\n const plain = tx ? tx.data === undefined && tx.serializedTx === undefined : typeof q.depositAddress === \"string\" && q.depositAddress !== \"\";\n if (plain && provider.depositAddressRoutes !== true) {\n return `${provider.name}: the route pays ${tx?.to || q.depositAddress}, an address the backend named, and the app did not allow deposit-address routes for this provider (remoteProvider depositAddressRoutes)`;\n }\n if (!chain.startsWith(\"eip155:\")) return null;\n if (!tx || tx.serializedTx !== undefined) return null;\n const pinned = (list: readonly string[] | undefined, address: string) =>\n (list ?? []).some((a) => typeof a === \"string\" && a.toLowerCase() === address.toLowerCase());\n if (tx.approvalAddress && !pinned(provider.spenders?.(chain), tx.approvalAddress)) {\n return `${provider.name}: the route approves ${tx.approvalAddress}, which the app did not pin as a spender on ${chain} (remoteProvider spenders)`;\n }\n if (tx.data !== undefined && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {\n return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;\n }\n if (tx.data !== undefined && provider.verifiesQuotes !== true && provider.verifiesBuilds !== true) {\n return `${provider.name}: the route's calldata is not checked by the app (remoteProvider verifyQuote / verifyBuild) — a pinned target alone does not say whom it pays`;\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -49,6 +49,7 @@ function remoteProvider(opts) {
|
|
|
49
49
|
name,
|
|
50
50
|
remote: true,
|
|
51
51
|
...opts.verifyBuild ? { verifiesBuilds: true } : {},
|
|
52
|
+
...opts.verifyQuote ? { verifiesQuotes: true } : {},
|
|
52
53
|
...opts.depositAddressRoutes === true ? { depositAddressRoutes: true } : {},
|
|
53
54
|
// The browser side has no dark-flag env; the backend enforces its own
|
|
54
55
|
// flags — a disabled provider simply quotes null / rejects build.
|
|
@@ -115,4 +116,4 @@ function remoteProvider(opts) {
|
|
|
115
116
|
export {
|
|
116
117
|
remoteProvider
|
|
117
118
|
};
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
119
|
+
//# sourceMappingURL=chunk-KB2GX6R2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/remote.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"./types.js\";\nimport {anySignal, timeoutSignal} from \"./util.js\";\n\n/**\n * HTTP-proxied `CrossChainProvider`: forwards every provider call to a backend\n * that runs the real provider. This is how a BROWSER context (e.g. a wallet's\n * signing worker) executes the full swap2 layer — account binding,\n * approvals, cell-body guards, id/status lifecycle — while provider secrets\n * (API JWTs, affiliate config) and non-CORS REST endpoints stay server-side.\n *\n * Wire protocol: POST {baseUrl}/{action} with a JSON body, one action per\n * provider method:\n * quote {provider, params: QuoteParams} → Quote | null\n * build {provider, quote: Quote} → UnsignedTx\n * preflight {provider, quote: Quote, tx: UnsignedTx} → {}\n * track {provider, ref: IntentRef} → IntentStatus\n * notify {provider, intentId, txHash} → {}\n * register {provider, intentId, signature} → {}\n * claim {provider, ref: IntentRef} → UnsignedTx | null\n * supports {provider, from, to} → {supported: boolean}\n *\n * `supportsRoute`/`enabled` need a SYNC answer, so they are config: pass the\n * routes the backend advertises via `supports`, or leave the default\n * allow-and-let-quote-decide behavior (a null quote is a clean miss).\n */\nexport interface RemoteProviderOptions {\n /** Provider name the backend multiplexes on (e.g. \"near-intents\"). */\n name: string;\n /** Endpoint prefix, e.g. \"/api/v2/swap2\" (same-origin) or absolute URL. */\n baseUrl: string;\n /** Sync capability gate; defaults to true (the backend quote decides). */\n supportsRoute?: (from: Asset, to: Asset) => boolean;\n /** Optional synchronous dark flag for accurate client-side discovery. */\n enabled?: () => boolean;\n /** Optional chain-level discovery hooks mirrored from the backend. */\n supportsChain?: (caip2: string) => boolean;\n supportsChainPair?: (fromChain: string, toChain: string) => boolean;\n /** Explicit opt-in when the backend can execute same-chain swaps. */\n supportsSameChain?: boolean;\n /**\n * The contracts this provider's claims may call, per chain (e.g. `cctpClaimTargets` from\n * `@anywalletkit/protocol-swap2-cctp`). The app's list, not the server's: without it the engine\n * refuses claims from this stub (round 3, M24).\n */\n claimTargets?: (chain: string) => readonly string[];\n /**\n * The contracts this provider's EVM swaps may call, and the spenders they may be approved for,\n * per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into\n * these options). The app's lists, not the server's: without them the engine refuses this stub's\n * EVM calldata routes and approvals, so a compromised backend cannot name its own contract or\n * spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need `depositAddressRoutes`.\n */\n callTargets?: (chain: string) => readonly string[];\n spenders?: (chain: string) => readonly string[];\n /**\n * The provider's own check of a quote's transaction, run here on the client against the request\n * the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the\n * auction. The backend runs the same check, but the backend is what this guards against.\n */\n verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;\n /**\n * The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built\n * for: the reason it fails, or null. A build it refuses is never approved or signed.\n */\n verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;\n /**\n * Let this stub's routes send the input to an address the backend names: a deposit address (NEAR Intents, a\n * THORChain or Chainflip Tron vault) or a plain value send. Off by default: such a route bypasses `callTargets`, so\n * a compromised backend could name its own address (scan 2026-10-28, F9). Opt in only for a provider whose deposit\n * addresses cannot be pinned, or whose `verifyQuote` / `verifyBuild` checks them (`thorchainPins`, `chainflipPins`).\n */\n depositAddressRoutes?: boolean;\n /**\n * Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this\n * channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).\n */\n allowInsecureBaseUrl?: boolean;\n /** Defaults to globalThis.fetch — injectable for tests. */\n fetchImpl?: typeof fetch;\n /** Extra headers (e.g. auth) attached to every call. */\n headers?: Record<string, string>;\n /** Per-request fail-closed timeout. Defaults to 12 seconds. */\n timeoutMs?: number;\n}\n\n/**\n * A provider that runs on your server: quotes, deposits and tracking go to `baseUrl`, so API keys\n * and affiliate settings never ship in the app. The engine still validates every quote and\n * transaction locally before signing.\n */\nexport function remoteProvider(opts: RemoteProviderOptions): CrossChainProvider {\n const name = opts.name;\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"swap2-remote: invalid provider name\");\n // Trimmed first: a URL parser drops leading spaces and tabs, so \" http://host\" would reach http (scan 2026-10-28 review).\n const baseUrl = opts.baseUrl.trim().replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\n // A same-origin path (\"/api/swap2\") inherits the page's scheme; an absolute URL must be https, and a\n // scheme-relative one (\"//host/…\") names another origin without saying how to reach it.\n const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\\/\\//i.test(baseUrl) : /^[\\\\/]{2}/.test(baseUrl);\n if (insecure && opts.allowInsecureBaseUrl !== true) {\n throw new Error(\"swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)\");\n }\n const supportsRoute = opts.supportsRoute;\n const enabled = opts.enabled;\n const headers = Object.freeze({...opts.headers});\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 12_000;\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60_000) {\n throw new Error(\"swap2-remote: timeoutMs must be an integer between 1 and 60000\");\n }\n\n // The request each quote answered, for verifyBuild (the build only carries the quote). Bounded: a stub quotes often.\n const requests = new Map<string, QuoteParams>();\n const remember = (intentId: string, params: QuoteParams) => {\n requests.delete(intentId);\n requests.set(intentId, params);\n if (requests.size > 64) requests.delete(requests.keys().next().value!);\n };\n\n async function call<T>(action: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n const timeout = timeoutSignal(timeoutMs);\n const requestSignal = signal ? anySignal([signal, timeout]) : timeout;\n const res = await doFetch(`${baseUrl}/${action}`, {\n method: \"POST\",\n headers: {...headers, \"content-type\": \"application/json\"},\n // UnsignedTx carries bigint fields (value, gasLimit) — JSON.stringify\n // throws on them (\"Do not know how to serialize a BigInt\", seen\n // 2026-10-10 on a Solana → Base preflight). Wire them as decimal strings.\n body: JSON.stringify({provider: name, ...body}, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)),\n signal: requestSignal,\n });\n if (!res.ok) {\n // Surface the backend's message — provider errors are actionable\n // (\"quote expired\", \"route unsupported\"), not just status codes.\n const text = await res.text().catch(() => \"\");\n throw new Error(`swap2-remote ${name}/${action}: ${res.status} ${text.slice(0, 300)}`);\n }\n return (await res.json()) as T;\n }\n\n return {\n name,\n remote: true,\n ...(opts.verifyBuild ? {verifiesBuilds: true} : {}),\n ...(opts.depositAddressRoutes === true ? {depositAddressRoutes: true} : {}),\n // The browser side has no dark-flag env; the backend enforces its own\n // flags — a disabled provider simply quotes null / rejects build.\n enabled: () => enabled ? enabled() : true,\n supportsRoute(from: Asset, to: Asset): boolean {\n return supportsRoute ? supportsRoute(from, to) : true;\n },\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const out = await call<Quote | {noQuote: string} | null>(\"quote\", {params}, signal);\n if (out && typeof out === \"object\" && \"noQuote\" in out) {\n // The backend forwarded the venue's own refusal — throw it so the\n // router records a REASON instead of an anonymous null.\n throw new Error(String((out as {noQuote: unknown}).noQuote).slice(0, 300));\n }\n if (out && typeof out === \"object\" && typeof out.intentId === \"string\") remember(out.intentId, params);\n if (out && opts.verifyQuote) {\n let refusal: string | null;\n try { refusal = opts.verifyQuote(params, out); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the quote fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = await call<UnsignedTx>(\"build\", {quote});\n if (opts.verifyBuild) {\n let refusal: string | null;\n try { refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId)); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the build fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return tx;\n },\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n await call<Record<string, never>>(\"preflight\", {quote, tx});\n },\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n return call<IntentStatus>(\"track\", {ref});\n },\n async notifyDeposit(intentId: string, txHash: string): Promise<void> {\n await call<Record<string, never>>(\"notify\", {intentId, txHash});\n },\n async registerSignedOrder(intentId: string, signature: string): Promise<void> {\n await call<Record<string, never>>(\"register\", {intentId, signature});\n },\n async buildClaim(ref: IntentRef): Promise<UnsignedTx | null> {\n const out = await call<UnsignedTx | null | Record<string, never>>(\"claim\", {ref});\n // A backend without the action (older deploy) answers {} — treat as \"not ready\".\n if (!out || typeof out !== \"object\" || !(\"to\" in out)) return null;\n return out as UnsignedTx;\n },\n ...(opts.supportsChain ? {supportsChain: opts.supportsChain.bind(opts)} : {}),\n ...(opts.supportsChainPair ? {supportsChainPair: opts.supportsChainPair.bind(opts)} : {}),\n ...(opts.supportsSameChain !== undefined ? {supportsSameChain: opts.supportsSameChain} : {}),\n ...(opts.claimTargets ? {claimTargets: opts.claimTargets} : {}),\n ...(opts.callTargets ? {callTargets: opts.callTargets} : {}),\n ...(opts.spenders ? {spenders: opts.spenders} : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/remote.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"./types.js\";\nimport {anySignal, timeoutSignal} from \"./util.js\";\n\n/**\n * HTTP-proxied `CrossChainProvider`: forwards every provider call to a backend\n * that runs the real provider. This is how a BROWSER context (e.g. a wallet's\n * signing worker) executes the full swap2 layer — account binding,\n * approvals, cell-body guards, id/status lifecycle — while provider secrets\n * (API JWTs, affiliate config) and non-CORS REST endpoints stay server-side.\n *\n * Wire protocol: POST {baseUrl}/{action} with a JSON body, one action per\n * provider method:\n * quote {provider, params: QuoteParams} → Quote | null\n * build {provider, quote: Quote} → UnsignedTx\n * preflight {provider, quote: Quote, tx: UnsignedTx} → {}\n * track {provider, ref: IntentRef} → IntentStatus\n * notify {provider, intentId, txHash} → {}\n * register {provider, intentId, signature} → {}\n * claim {provider, ref: IntentRef} → UnsignedTx | null\n * supports {provider, from, to} → {supported: boolean}\n *\n * `supportsRoute`/`enabled` need a SYNC answer, so they are config: pass the\n * routes the backend advertises via `supports`, or leave the default\n * allow-and-let-quote-decide behavior (a null quote is a clean miss).\n */\nexport interface RemoteProviderOptions {\n /** Provider name the backend multiplexes on (e.g. \"near-intents\"). */\n name: string;\n /** Endpoint prefix, e.g. \"/api/v2/swap2\" (same-origin) or absolute URL. */\n baseUrl: string;\n /** Sync capability gate; defaults to true (the backend quote decides). */\n supportsRoute?: (from: Asset, to: Asset) => boolean;\n /** Optional synchronous dark flag for accurate client-side discovery. */\n enabled?: () => boolean;\n /** Optional chain-level discovery hooks mirrored from the backend. */\n supportsChain?: (caip2: string) => boolean;\n supportsChainPair?: (fromChain: string, toChain: string) => boolean;\n /** Explicit opt-in when the backend can execute same-chain swaps. */\n supportsSameChain?: boolean;\n /**\n * The contracts this provider's claims may call, per chain (e.g. `cctpClaimTargets` from\n * `@anywalletkit/protocol-swap2-cctp`). The app's list, not the server's: without it the engine\n * refuses claims from this stub (round 3, M24).\n */\n claimTargets?: (chain: string) => readonly string[];\n /**\n * The contracts this provider's EVM swaps may call, and the spenders they may be approved for,\n * per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into\n * these options). The app's lists, not the server's: without them the engine refuses this stub's\n * EVM calldata routes and approvals, so a compromised backend cannot name its own contract or\n * spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need `depositAddressRoutes`.\n */\n callTargets?: (chain: string) => readonly string[];\n spenders?: (chain: string) => readonly string[];\n /**\n * The provider's own check of a quote's transaction, run here on the client against the request\n * the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the\n * auction. The backend runs the same check, but the backend is what this guards against.\n */\n verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;\n /**\n * The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built\n * for: the reason it fails, or null. A build it refuses is never approved or signed.\n */\n verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;\n /**\n * Let this stub's routes send the input to an address the backend names: a deposit address (NEAR Intents, a\n * THORChain or Chainflip Tron vault) or a plain value send. Off by default: such a route bypasses `callTargets`, so\n * a compromised backend could name its own address (scan 2026-10-28, F9). Opt in only for a provider whose deposit\n * addresses cannot be pinned, or whose `verifyQuote` / `verifyBuild` checks them (`thorchainPins`, `chainflipPins`).\n */\n depositAddressRoutes?: boolean;\n /**\n * Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this\n * channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).\n */\n allowInsecureBaseUrl?: boolean;\n /** Defaults to globalThis.fetch — injectable for tests. */\n fetchImpl?: typeof fetch;\n /** Extra headers (e.g. auth) attached to every call. */\n headers?: Record<string, string>;\n /** Per-request fail-closed timeout. Defaults to 12 seconds. */\n timeoutMs?: number;\n}\n\n/**\n * A provider that runs on your server: quotes, deposits and tracking go to `baseUrl`, so API keys\n * and affiliate settings never ship in the app. The engine still validates every quote and\n * transaction locally before signing.\n */\nexport function remoteProvider(opts: RemoteProviderOptions): CrossChainProvider {\n const name = opts.name;\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"swap2-remote: invalid provider name\");\n // Trimmed first: a URL parser drops leading spaces and tabs, so \" http://host\" would reach http (scan 2026-10-28 review).\n const baseUrl = opts.baseUrl.trim().replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\n // A same-origin path (\"/api/swap2\") inherits the page's scheme; an absolute URL must be https, and a\n // scheme-relative one (\"//host/…\") names another origin without saying how to reach it.\n const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\\/\\//i.test(baseUrl) : /^[\\\\/]{2}/.test(baseUrl);\n if (insecure && opts.allowInsecureBaseUrl !== true) {\n throw new Error(\"swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)\");\n }\n const supportsRoute = opts.supportsRoute;\n const enabled = opts.enabled;\n const headers = Object.freeze({...opts.headers});\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 12_000;\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60_000) {\n throw new Error(\"swap2-remote: timeoutMs must be an integer between 1 and 60000\");\n }\n\n // The request each quote answered, for verifyBuild (the build only carries the quote). Bounded: a stub quotes often.\n const requests = new Map<string, QuoteParams>();\n const remember = (intentId: string, params: QuoteParams) => {\n requests.delete(intentId);\n requests.set(intentId, params);\n if (requests.size > 64) requests.delete(requests.keys().next().value!);\n };\n\n async function call<T>(action: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n const timeout = timeoutSignal(timeoutMs);\n const requestSignal = signal ? anySignal([signal, timeout]) : timeout;\n const res = await doFetch(`${baseUrl}/${action}`, {\n method: \"POST\",\n headers: {...headers, \"content-type\": \"application/json\"},\n // UnsignedTx carries bigint fields (value, gasLimit) — JSON.stringify\n // throws on them (\"Do not know how to serialize a BigInt\", seen\n // 2026-10-10 on a Solana → Base preflight). Wire them as decimal strings.\n body: JSON.stringify({provider: name, ...body}, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)),\n signal: requestSignal,\n });\n if (!res.ok) {\n // Surface the backend's message — provider errors are actionable\n // (\"quote expired\", \"route unsupported\"), not just status codes.\n const text = await res.text().catch(() => \"\");\n throw new Error(`swap2-remote ${name}/${action}: ${res.status} ${text.slice(0, 300)}`);\n }\n return (await res.json()) as T;\n }\n\n return {\n name,\n remote: true,\n ...(opts.verifyBuild ? {verifiesBuilds: true} : {}),\n ...(opts.verifyQuote ? {verifiesQuotes: true} : {}),\n ...(opts.depositAddressRoutes === true ? {depositAddressRoutes: true} : {}),\n // The browser side has no dark-flag env; the backend enforces its own\n // flags — a disabled provider simply quotes null / rejects build.\n enabled: () => enabled ? enabled() : true,\n supportsRoute(from: Asset, to: Asset): boolean {\n return supportsRoute ? supportsRoute(from, to) : true;\n },\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const out = await call<Quote | {noQuote: string} | null>(\"quote\", {params}, signal);\n if (out && typeof out === \"object\" && \"noQuote\" in out) {\n // The backend forwarded the venue's own refusal — throw it so the\n // router records a REASON instead of an anonymous null.\n throw new Error(String((out as {noQuote: unknown}).noQuote).slice(0, 300));\n }\n if (out && typeof out === \"object\" && typeof out.intentId === \"string\") remember(out.intentId, params);\n if (out && opts.verifyQuote) {\n let refusal: string | null;\n try { refusal = opts.verifyQuote(params, out); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the quote fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = await call<UnsignedTx>(\"build\", {quote});\n if (opts.verifyBuild) {\n let refusal: string | null;\n try { refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId)); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the build fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return tx;\n },\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n await call<Record<string, never>>(\"preflight\", {quote, tx});\n },\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n return call<IntentStatus>(\"track\", {ref});\n },\n async notifyDeposit(intentId: string, txHash: string): Promise<void> {\n await call<Record<string, never>>(\"notify\", {intentId, txHash});\n },\n async registerSignedOrder(intentId: string, signature: string): Promise<void> {\n await call<Record<string, never>>(\"register\", {intentId, signature});\n },\n async buildClaim(ref: IntentRef): Promise<UnsignedTx | null> {\n const out = await call<UnsignedTx | null | Record<string, never>>(\"claim\", {ref});\n // A backend without the action (older deploy) answers {} — treat as \"not ready\".\n if (!out || typeof out !== \"object\" || !(\"to\" in out)) return null;\n return out as UnsignedTx;\n },\n ...(opts.supportsChain ? {supportsChain: opts.supportsChain.bind(opts)} : {}),\n ...(opts.supportsChainPair ? {supportsChainPair: opts.supportsChainPair.bind(opts)} : {}),\n ...(opts.supportsSameChain !== undefined ? {supportsSameChain: opts.supportsSameChain} : {}),\n ...(opts.claimTargets ? {claimTargets: opts.claimTargets} : {}),\n ...(opts.callTargets ? {callTargets: opts.callTargets} : {}),\n ...(opts.spenders ? {spenders: opts.spenders} : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
Swap2UnsupportedOperationError,
|
|
11
11
|
Swap2ValidationError
|
|
12
12
|
} from "./chunk-IVR3SREQ.js";
|
|
13
|
+
import {
|
|
14
|
+
SLIPPAGE_ROUNDING_BPS
|
|
15
|
+
} from "./chunk-F5TRF52T.js";
|
|
13
16
|
import {
|
|
14
17
|
tonRawAddress
|
|
15
18
|
} from "./chunk-2KC734UX.js";
|
|
@@ -196,16 +199,18 @@ function reviewedIdentity(plan) {
|
|
|
196
199
|
return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
197
200
|
}
|
|
198
201
|
var SWAP2_ID_PREFIX = "awk1";
|
|
199
|
-
|
|
202
|
+
var CONFIRMED_SEGMENT = "ok";
|
|
203
|
+
function encodeId(provider, intentId, sourceTxHash, destTxHash, destTxConfirmed = false) {
|
|
200
204
|
const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
|
|
201
|
-
const
|
|
205
|
+
const dest = destTxHash != null ? [destTxHash, ...destTxConfirmed ? [CONFIRMED_SEGMENT] : []] : [];
|
|
206
|
+
const parts = [provider, intentId, ...sourceTxHash != null ? [sourceTxHash, ...dest] : []];
|
|
202
207
|
return `${SWAP2_ID_PREFIX}.${parts.map(enc).join(".")}`;
|
|
203
208
|
}
|
|
204
209
|
function decodeId(id) {
|
|
205
210
|
try {
|
|
206
211
|
if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) throw new Error("unsupported id version");
|
|
207
212
|
const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
|
|
208
|
-
if (parts.length < 2 || parts.length >
|
|
213
|
+
if (parts.length < 2 || parts.length > 5 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
|
|
209
214
|
const provider = decodeURIComponent(parts[0]);
|
|
210
215
|
const intentId = decodeURIComponent(parts[1]);
|
|
211
216
|
const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
|
|
@@ -213,7 +218,8 @@ function decodeId(id) {
|
|
|
213
218
|
if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash || parts[3] !== void 0 && !destTxHash) {
|
|
214
219
|
throw new Error("invalid id contents");
|
|
215
220
|
}
|
|
216
|
-
|
|
221
|
+
if (parts[4] !== void 0 && parts[4] !== CONFIRMED_SEGMENT) throw new Error("invalid id contents");
|
|
222
|
+
return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {}, ...parts[4] !== void 0 ? { destTxConfirmed: true } : {} };
|
|
217
223
|
} catch {
|
|
218
224
|
throw new Swap2ValidationError("swap2: malformed swap2 status id");
|
|
219
225
|
}
|
|
@@ -346,13 +352,30 @@ function assertQuoteConstraints(q, options) {
|
|
|
346
352
|
}
|
|
347
353
|
if (options.slippage !== void 0) {
|
|
348
354
|
const scale = 1000000000n;
|
|
349
|
-
const permitted = BigInt(floorScaled(options.slippage, 1e9));
|
|
350
|
-
const required = nominal * (scale - permitted) / scale;
|
|
355
|
+
const permitted = BigInt(floorScaled(options.slippage, 1e9)) + BigInt(SLIPPAGE_ROUNDING_BPS) * 100000n;
|
|
356
|
+
const required = permitted >= scale ? 0n : nominal * (scale - permitted) / scale;
|
|
351
357
|
if (guaranteed < required) {
|
|
352
358
|
throw new Error(`swap2: quote exceeds caller slippage (${options.slippage})`);
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
361
|
}
|
|
362
|
+
function bridgeOutOf(params, q, toTokenDecimals) {
|
|
363
|
+
let decimals = resolveAsset(params.destChain, params.destToken)?.decimals;
|
|
364
|
+
if (decimals === void 0 && toTokenDecimals !== void 0) {
|
|
365
|
+
if (q.destDecimals !== void 0 && q.destDecimals !== toTokenDecimals) {
|
|
366
|
+
throw new Swap2ValidationError(`swap2: the quote's destDecimals ${q.destDecimals} do not match toTokenDecimals ${toTokenDecimals}`);
|
|
367
|
+
}
|
|
368
|
+
decimals = toTokenDecimals;
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
destChain: params.destChain,
|
|
372
|
+
recipient: params.destAddress,
|
|
373
|
+
destToken: params.destToken,
|
|
374
|
+
minAmountOut: q.minDestAmount,
|
|
375
|
+
...decimals !== void 0 ? { destDecimals: decimals } : {},
|
|
376
|
+
...params.slippageBps !== void 0 ? { slippageBps: params.slippageBps } : {}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
356
379
|
function requireExecutionFloor(options) {
|
|
357
380
|
if (options.minAmountOut === void 0) {
|
|
358
381
|
throw new Error("swap2: swap2() requires caller-supplied minAmountOut");
|
|
@@ -451,6 +474,10 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
|
|
|
451
474
|
}
|
|
452
475
|
const destChain = toCaip2(options.toChain, this.fromChain);
|
|
453
476
|
const constraints = callerConstraints(options);
|
|
477
|
+
const toDecimals = options.toTokenDecimals;
|
|
478
|
+
if (toDecimals !== void 0 && (!Number.isInteger(toDecimals) || toDecimals < 0 || toDecimals > 36)) {
|
|
479
|
+
throw new Swap2ValidationError("swap2: toTokenDecimals must be whole decimals in 0..36");
|
|
480
|
+
}
|
|
454
481
|
const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
|
|
455
482
|
const source = this.sourceAsset(options);
|
|
456
483
|
const params = {
|
|
@@ -793,6 +820,8 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
|
|
|
793
820
|
}
|
|
794
821
|
} else if (remote && !pinnedTargets.includes(unsigned.to.toLowerCase())) {
|
|
795
822
|
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' calls ${getAddress(unsigned.to)} on ${this.fromChain}, which the app did not pin as a call target (remoteProvider callTargets) \u2014 refused`);
|
|
823
|
+
} else if (remote && provider.verifiesQuotes !== true && provider.verifiesBuilds !== true) {
|
|
824
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' calls ${getAddress(unsigned.to)} with calldata the app does not check (remoteProvider verifyQuote / verifyBuild) \u2014 refused`);
|
|
796
825
|
}
|
|
797
826
|
}
|
|
798
827
|
const needsApproval = !!unsigned.approvalAddress && !!source.contract;
|
|
@@ -978,7 +1007,8 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
|
|
|
978
1007
|
maxValueOut: ((source.contract ? 0n : BigInt(q.sourceAmount)) + allowance).toString(),
|
|
979
1008
|
// Only a same-chain swap lands in this account; a bridge delivers elsewhere.
|
|
980
1009
|
...params.destChain === this.fromChain ? { tokenIn: { token: params.destToken, minAmount: q.minDestAmount } } : {},
|
|
981
|
-
...source.contract ? { source: { token: source.contract, maxAmount: q.sourceAmount } } : {}
|
|
1010
|
+
...source.contract ? { source: { token: source.contract, maxAmount: q.sourceAmount } } : {},
|
|
1011
|
+
...params.destChain !== this.fromChain ? { bridgeOut: bridgeOutOf(params, q, executionOptions.toTokenDecimals) } : {}
|
|
982
1012
|
};
|
|
983
1013
|
sent = await signer.sendSerializedTransaction(unsigned.serializedTx, expect);
|
|
984
1014
|
} else if (tokenId) {
|
|
@@ -1062,10 +1092,10 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
|
|
|
1062
1092
|
}
|
|
1063
1093
|
/** Status of a swap by the id `swap2()` returned, asked from the provider that executed it. */
|
|
1064
1094
|
async getSwap2Status(id, _options) {
|
|
1065
|
-
const { provider: name, intentId, sourceTxHash, destTxHash } = decodeId(id);
|
|
1095
|
+
const { provider: name, intentId, sourceTxHash, destTxHash, destTxConfirmed } = decodeId(id);
|
|
1066
1096
|
const provider = this.providerFor(name);
|
|
1067
1097
|
if (!provider) throw new Error(`swap2: no swap2 found for id '${id}'`);
|
|
1068
|
-
return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash } : {} }));
|
|
1098
|
+
return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash, ...destTxConfirmed ? { destTxConfirmed } : {} } : {} }));
|
|
1069
1099
|
}
|
|
1070
1100
|
/**
|
|
1071
1101
|
* Two-phase routes (CCTP-style burn-and-mint): once `getSwap2Status` reports
|
|
@@ -1138,7 +1168,21 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
|
|
|
1138
1168
|
});
|
|
1139
1169
|
if (!sent.hash) throw new Swap2ExecutionError("swap2: account did not return a claim transaction hash");
|
|
1140
1170
|
this.emitPhase("broadcast", { hash: sent.hash });
|
|
1141
|
-
|
|
1171
|
+
let confirmed = false;
|
|
1172
|
+
if (typeof signer.waitForTransaction === "function") {
|
|
1173
|
+
try {
|
|
1174
|
+
await signer.waitForTransaction(sent.hash);
|
|
1175
|
+
confirmed = true;
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
const err = e;
|
|
1178
|
+
const timedOut = /timeout|timed out/i.test(String(err?.name ?? "")) || /timed out|timeout/i.test(String(err?.message ?? ""));
|
|
1179
|
+
const reverted = err?.code === "reverted" || !timedOut && /\breverted\b/i.test(String(err?.message ?? ""));
|
|
1180
|
+
if (reverted) {
|
|
1181
|
+
throw new Swap2ExecutionError(`swap2: the claim transaction ${sent.hash} on ${unsigned.chain} reverted \u2014 poll the status of '${id}' and claim again if it is still action-required`, { cause: e });
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return { hash: sent.hash, chain: unsigned.chain, id: encodeId(name, intentId, sourceTxHash, sent.hash, confirmed) };
|
|
1142
1186
|
}
|
|
1143
1187
|
/** The inherited bridge() has no floor field: quote first, then bind execution
|
|
1144
1188
|
* to that quote's minimum so the inherited convenience API stays safe. */
|
|
@@ -1215,7 +1259,10 @@ function structuredCloneSafe(value) {
|
|
|
1215
1259
|
if (value === null || typeof value !== "object") return value;
|
|
1216
1260
|
if (Array.isArray(value)) return value.map((v) => structuredCloneSafe(v));
|
|
1217
1261
|
const out = {};
|
|
1218
|
-
for (const [k, v] of Object.entries(value))
|
|
1262
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1263
|
+
if (k === "__proto__") Object.defineProperty(out, k, { value: structuredCloneSafe(v), writable: true, enumerable: true, configurable: true });
|
|
1264
|
+
else out[k] = structuredCloneSafe(v);
|
|
1265
|
+
}
|
|
1219
1266
|
return out;
|
|
1220
1267
|
}
|
|
1221
1268
|
function deepFreeze(value) {
|
|
@@ -1236,4 +1283,4 @@ export {
|
|
|
1236
1283
|
swap2ChainId,
|
|
1237
1284
|
BaseSwap2Protocol
|
|
1238
1285
|
};
|
|
1239
|
-
//# sourceMappingURL=chunk-
|
|
1286
|
+
//# sourceMappingURL=chunk-PN76KVPT.js.map
|