@anywalletkit/protocol-swap2-core 0.6.0 → 0.7.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 +1 -1
- package/dist/chunk-2KC734UX.js +117 -0
- package/dist/chunk-2KC734UX.js.map +1 -0
- package/dist/{chunk-IGTMUTNJ.js → chunk-5E5FWYZQ.js} +123 -6
- package/dist/chunk-5E5FWYZQ.js.map +1 -0
- package/dist/{chunk-ZMWK3RQ4.js → chunk-CT2REW2V.js} +39 -5
- package/dist/chunk-CT2REW2V.js.map +1 -0
- package/dist/{chunk-JFGMF6FO.js → chunk-U653ZREM.js} +9 -3
- package/dist/chunk-U653ZREM.js.map +1 -0
- package/dist/{chunk-KIAQXIBG.js → chunk-WBYCNN6S.js} +16 -2
- package/dist/chunk-WBYCNN6S.js.map +1 -0
- package/dist/index.cjs +243 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -6
- package/dist/remote.cjs +37 -3
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.d.cts +26 -1
- package/dist/remote.d.ts +26 -1
- package/dist/remote.js +2 -2
- package/dist/server.cjs +23 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +6 -5
- package/dist/server.js.map +1 -1
- package/dist/swap2/base.cjs +180 -15
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.d.cts +19 -1
- package/dist/swap2/base.d.ts +19 -1
- package/dist/swap2/base.js +2 -1
- package/dist/swap2/single.cjs +198 -15
- package/dist/swap2/single.cjs.map +1 -1
- package/dist/swap2/single.js +4 -3
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +32 -1
- package/dist/types.d.ts +32 -1
- package/dist/util.cjs +70 -2
- package/dist/util.cjs.map +1 -1
- package/dist/util.d.cts +13 -2
- package/dist/util.d.ts +13 -2
- package/dist/util.js +7 -3
- package/dist/validate.cjs +14 -0
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +10 -2
- package/dist/validate.d.ts +10 -2
- package/dist/validate.js +3 -1
- package/package.json +9 -7
- package/dist/chunk-IGTMUTNJ.js.map +0 -1
- package/dist/chunk-JFGMF6FO.js.map +0 -1
- package/dist/chunk-KIAQXIBG.js.map +0 -1
- package/dist/chunk-MNJTMSFN.js +0 -49
- package/dist/chunk-MNJTMSFN.js.map +0 -1
- package/dist/chunk-ZMWK3RQ4.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { Asset, CrossChainProvider, IntentRef, IntentState, IntentStatus, Quote, QuoteParams, RoutedQuote, ScoreWeights, TxRequest, UnsignedTx } from './types.cjs';
|
|
1
|
+
export { Asset, CrossChainProvider, IntentRef, IntentState, IntentStatus, Quote, QuoteParams, RemotePins, RoutedQuote, ScoreWeights, TxRequest, UnsignedTx } from './types.cjs';
|
|
2
2
|
export { Swap2ConfigurationError, Swap2Error, Swap2ErrorCode, Swap2ExecutionError, Swap2FeeLimitError, Swap2InFlightError, Swap2InsufficientGasError, Swap2MaybeSentError, Swap2QuoteError, Swap2StatusError, Swap2UnsupportedOperationError, Swap2ValidationError } from './errors.cjs';
|
|
3
|
-
export { validQuote } from './validate.cjs';
|
|
3
|
+
export { remotePinRefusal, validQuote } from './validate.cjs';
|
|
4
4
|
export { RemoteProviderOptions, remoteProvider } from './remote.cjs';
|
|
5
|
-
export { evmRef, isEvm, namespace, redactUrls, sourceAssetOf } from './util.cjs';
|
|
6
|
-
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, SerializedExecutionExpectation, Swap2Account, Swap2EngineConfig, Swap2ExecutionPlan, nativeDecimals, nativeUnits, swap2ChainId } from './swap2/base.cjs';
|
|
5
|
+
export { evmRef, isEvm, namespace, redactUrls, sourceAssetOf, tonRawAddress } from './util.cjs';
|
|
6
|
+
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, SerializedExecutionExpectation, Swap2Account, Swap2EngineConfig, Swap2ExecutionPhase, Swap2ExecutionPhaseDetail, Swap2ExecutionPlan, TonBodySummary, nativeDecimals, nativeUnits, swap2ChainId } from './swap2/base.cjs';
|
|
7
7
|
export { SingleProviderSwap2 } from './swap2/single.cjs';
|
|
8
8
|
export { SwapActivityOptions, assetLabel, assetMetaFor, callSelector, calledContract, callsContract, chainLabel, describeSwapLike, matchesCall, ownerAssets, shortAddress } from './activity.cjs';
|
|
9
9
|
export { SlippageClass, SlippageContext, SlippageOptions, SlippageSuggestion, slippageClassOf, suggestSlippage } from './slippage.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { Asset, CrossChainProvider, IntentRef, IntentState, IntentStatus, Quote, QuoteParams, RoutedQuote, ScoreWeights, TxRequest, UnsignedTx } from './types.js';
|
|
1
|
+
export { Asset, CrossChainProvider, IntentRef, IntentState, IntentStatus, Quote, QuoteParams, RemotePins, RoutedQuote, ScoreWeights, TxRequest, UnsignedTx } from './types.js';
|
|
2
2
|
export { Swap2ConfigurationError, Swap2Error, Swap2ErrorCode, Swap2ExecutionError, Swap2FeeLimitError, Swap2InFlightError, Swap2InsufficientGasError, Swap2MaybeSentError, Swap2QuoteError, Swap2StatusError, Swap2UnsupportedOperationError, Swap2ValidationError } from './errors.js';
|
|
3
|
-
export { validQuote } from './validate.js';
|
|
3
|
+
export { remotePinRefusal, validQuote } from './validate.js';
|
|
4
4
|
export { RemoteProviderOptions, remoteProvider } from './remote.js';
|
|
5
|
-
export { evmRef, isEvm, namespace, redactUrls, sourceAssetOf } from './util.js';
|
|
6
|
-
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, SerializedExecutionExpectation, Swap2Account, Swap2EngineConfig, Swap2ExecutionPlan, nativeDecimals, nativeUnits, swap2ChainId } from './swap2/base.js';
|
|
5
|
+
export { evmRef, isEvm, namespace, redactUrls, sourceAssetOf, tonRawAddress } from './util.js';
|
|
6
|
+
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, SerializedExecutionExpectation, Swap2Account, Swap2EngineConfig, Swap2ExecutionPhase, Swap2ExecutionPhaseDetail, Swap2ExecutionPlan, TonBodySummary, nativeDecimals, nativeUnits, swap2ChainId } from './swap2/base.js';
|
|
7
7
|
export { SingleProviderSwap2 } from './swap2/single.js';
|
|
8
8
|
export { SwapActivityOptions, assetLabel, assetMetaFor, callSelector, calledContract, callsContract, chainLabel, describeSwapLike, matchesCall, ownerAssets, shortAddress } from './activity.js';
|
|
9
9
|
export { SlippageClass, SlippageContext, SlippageOptions, SlippageSuggestion, slippageClassOf, suggestSlippage } from './slippage.js';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-J4WXUNK2.js";
|
|
13
13
|
import {
|
|
14
14
|
SingleProviderSwap2
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-U653ZREM.js";
|
|
16
16
|
import {
|
|
17
17
|
BaseSwap2Protocol,
|
|
18
18
|
NATIVE_DECIMALS,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
nativeDecimals,
|
|
22
22
|
nativeUnits,
|
|
23
23
|
swap2ChainId
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-5E5FWYZQ.js";
|
|
25
25
|
import "./chunk-55J6XMHW.js";
|
|
26
26
|
import {
|
|
27
27
|
Swap2ConfigurationError,
|
|
@@ -37,22 +37,24 @@ import {
|
|
|
37
37
|
Swap2ValidationError
|
|
38
38
|
} from "./chunk-IVR3SREQ.js";
|
|
39
39
|
import {
|
|
40
|
+
remotePinRefusal,
|
|
40
41
|
validQuote
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-WBYCNN6S.js";
|
|
42
43
|
import {
|
|
43
44
|
slippageClassOf,
|
|
44
45
|
suggestSlippage
|
|
45
46
|
} from "./chunk-VNVBGY7U.js";
|
|
46
47
|
import {
|
|
47
48
|
remoteProvider
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-CT2REW2V.js";
|
|
49
50
|
import {
|
|
50
51
|
evmRef,
|
|
51
52
|
isEvm,
|
|
52
53
|
namespace,
|
|
53
54
|
redactUrls,
|
|
54
|
-
sourceAssetOf
|
|
55
|
-
|
|
55
|
+
sourceAssetOf,
|
|
56
|
+
tonRawAddress
|
|
57
|
+
} from "./chunk-2KC734UX.js";
|
|
56
58
|
export {
|
|
57
59
|
BaseSwap2Protocol,
|
|
58
60
|
NATIVE_DECIMALS,
|
|
@@ -85,12 +87,14 @@ export {
|
|
|
85
87
|
nativeUnits,
|
|
86
88
|
ownerAssets,
|
|
87
89
|
redactUrls,
|
|
90
|
+
remotePinRefusal,
|
|
88
91
|
remoteProvider,
|
|
89
92
|
shortAddress,
|
|
90
93
|
slippageClassOf,
|
|
91
94
|
sourceAssetOf,
|
|
92
95
|
suggestSlippage,
|
|
93
96
|
swap2ChainId,
|
|
97
|
+
tonRawAddress,
|
|
94
98
|
validQuote
|
|
95
99
|
};
|
|
96
100
|
//# sourceMappingURL=index.js.map
|
package/dist/remote.cjs
CHANGED
|
@@ -51,8 +51,12 @@ function anySignal(signals) {
|
|
|
51
51
|
function remoteProvider(opts) {
|
|
52
52
|
const name = opts.name;
|
|
53
53
|
if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error("swap2-remote: invalid provider name");
|
|
54
|
-
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
54
|
+
const baseUrl = opts.baseUrl.trim().replace(/\/+$/, "");
|
|
55
55
|
if (!baseUrl) throw new Error("swap2-remote: baseUrl is required");
|
|
56
|
+
const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\/\//i.test(baseUrl) : /^[\\/]{2}/.test(baseUrl);
|
|
57
|
+
if (insecure && opts.allowInsecureBaseUrl !== true) {
|
|
58
|
+
throw new Error("swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)");
|
|
59
|
+
}
|
|
56
60
|
const supportsRoute = opts.supportsRoute;
|
|
57
61
|
const enabled = opts.enabled;
|
|
58
62
|
const headers = Object.freeze({ ...opts.headers });
|
|
@@ -61,6 +65,12 @@ function remoteProvider(opts) {
|
|
|
61
65
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 6e4) {
|
|
62
66
|
throw new Error("swap2-remote: timeoutMs must be an integer between 1 and 60000");
|
|
63
67
|
}
|
|
68
|
+
const requests = /* @__PURE__ */ new Map();
|
|
69
|
+
const remember = (intentId, params) => {
|
|
70
|
+
requests.delete(intentId);
|
|
71
|
+
requests.set(intentId, params);
|
|
72
|
+
if (requests.size > 64) requests.delete(requests.keys().next().value);
|
|
73
|
+
};
|
|
64
74
|
async function call(action, body, signal) {
|
|
65
75
|
const timeout = timeoutSignal(timeoutMs);
|
|
66
76
|
const requestSignal = signal ? anySignal([signal, timeout]) : timeout;
|
|
@@ -81,6 +91,8 @@ function remoteProvider(opts) {
|
|
|
81
91
|
}
|
|
82
92
|
return {
|
|
83
93
|
name,
|
|
94
|
+
remote: true,
|
|
95
|
+
...opts.verifyBuild ? { verifiesBuilds: true } : {},
|
|
84
96
|
// The browser side has no dark-flag env; the backend enforces its own
|
|
85
97
|
// flags — a disabled provider simply quotes null / rejects build.
|
|
86
98
|
enabled: () => enabled ? enabled() : true,
|
|
@@ -92,10 +104,30 @@ function remoteProvider(opts) {
|
|
|
92
104
|
if (out && typeof out === "object" && "noQuote" in out) {
|
|
93
105
|
throw new Error(String(out.noQuote).slice(0, 300));
|
|
94
106
|
}
|
|
107
|
+
if (out && typeof out === "object" && typeof out.intentId === "string") remember(out.intentId, params);
|
|
108
|
+
if (out && opts.verifyQuote) {
|
|
109
|
+
let refusal;
|
|
110
|
+
try {
|
|
111
|
+
refusal = opts.verifyQuote(params, out);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
refusal = e instanceof Error ? e.message : String(e);
|
|
114
|
+
}
|
|
115
|
+
if (refusal) throw new Error(`${name}: the quote fails this app's own check \u2014 ${refusal.slice(0, 240)}`);
|
|
116
|
+
}
|
|
95
117
|
return out;
|
|
96
118
|
},
|
|
97
119
|
async buildDeposit(quote) {
|
|
98
|
-
|
|
120
|
+
const tx = await call("build", { quote });
|
|
121
|
+
if (opts.verifyBuild) {
|
|
122
|
+
let refusal;
|
|
123
|
+
try {
|
|
124
|
+
refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId));
|
|
125
|
+
} catch (e) {
|
|
126
|
+
refusal = e instanceof Error ? e.message : String(e);
|
|
127
|
+
}
|
|
128
|
+
if (refusal) throw new Error(`${name}: the build fails this app's own check \u2014 ${refusal.slice(0, 240)}`);
|
|
129
|
+
}
|
|
130
|
+
return tx;
|
|
99
131
|
},
|
|
100
132
|
async preflightDeposit(quote, tx) {
|
|
101
133
|
await call("preflight", { quote, tx });
|
|
@@ -117,7 +149,9 @@ function remoteProvider(opts) {
|
|
|
117
149
|
...opts.supportsChain ? { supportsChain: opts.supportsChain.bind(opts) } : {},
|
|
118
150
|
...opts.supportsChainPair ? { supportsChainPair: opts.supportsChainPair.bind(opts) } : {},
|
|
119
151
|
...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {},
|
|
120
|
-
...opts.claimTargets ? { claimTargets: opts.claimTargets } : {}
|
|
152
|
+
...opts.claimTargets ? { claimTargets: opts.claimTargets } : {},
|
|
153
|
+
...opts.callTargets ? { callTargets: opts.callTargets } : {},
|
|
154
|
+
...opts.spenders ? { spenders: opts.spenders } : {}
|
|
121
155
|
};
|
|
122
156
|
}
|
|
123
157
|
//# sourceMappingURL=remote.cjs.map
|
package/dist/remote.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/remote.ts","../src/util.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 /** 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 const baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\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 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 // 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 return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n return call<UnsignedTx>(\"build\", {quote});\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 };\n}\n","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8).\n */\nexport function redactUrls(text: string): string {\n return text.replace(/\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s\"'<>]+/gi, \"<url>\");\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/remote.ts","../src/util.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 no pins.\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 * 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 // 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","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/remote.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Asset, CrossChainProvider } from './types.cjs';
|
|
1
|
+
import { Asset, QuoteParams, Quote, UnsignedTx, CrossChainProvider } from './types.cjs';
|
|
2
2
|
import '@anywalletkit/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -43,6 +43,31 @@ interface RemoteProviderOptions {
|
|
|
43
43
|
* refuses claims from this stub (round 3, M24).
|
|
44
44
|
*/
|
|
45
45
|
claimTargets?: (chain: string) => readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* The contracts this provider's EVM swaps may call, and the spenders they may be approved for,
|
|
48
|
+
* per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into
|
|
49
|
+
* these options). The app's lists, not the server's: without them the engine refuses this stub's
|
|
50
|
+
* EVM calldata routes and approvals, so a compromised backend cannot name its own contract or
|
|
51
|
+
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need no pins.
|
|
52
|
+
*/
|
|
53
|
+
callTargets?: (chain: string) => readonly string[];
|
|
54
|
+
spenders?: (chain: string) => readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* The provider's own check of a quote's transaction, run here on the client against the request
|
|
57
|
+
* the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the
|
|
58
|
+
* auction. The backend runs the same check, but the backend is what this guards against.
|
|
59
|
+
*/
|
|
60
|
+
verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;
|
|
61
|
+
/**
|
|
62
|
+
* The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built
|
|
63
|
+
* for: the reason it fails, or null. A build it refuses is never approved or signed.
|
|
64
|
+
*/
|
|
65
|
+
verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this
|
|
68
|
+
* channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).
|
|
69
|
+
*/
|
|
70
|
+
allowInsecureBaseUrl?: boolean;
|
|
46
71
|
/** Defaults to globalThis.fetch — injectable for tests. */
|
|
47
72
|
fetchImpl?: typeof fetch;
|
|
48
73
|
/** Extra headers (e.g. auth) attached to every call. */
|
package/dist/remote.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Asset, CrossChainProvider } from './types.js';
|
|
1
|
+
import { Asset, QuoteParams, Quote, UnsignedTx, CrossChainProvider } from './types.js';
|
|
2
2
|
import '@anywalletkit/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -43,6 +43,31 @@ interface RemoteProviderOptions {
|
|
|
43
43
|
* refuses claims from this stub (round 3, M24).
|
|
44
44
|
*/
|
|
45
45
|
claimTargets?: (chain: string) => readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* The contracts this provider's EVM swaps may call, and the spenders they may be approved for,
|
|
48
|
+
* per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into
|
|
49
|
+
* these options). The app's lists, not the server's: without them the engine refuses this stub's
|
|
50
|
+
* EVM calldata routes and approvals, so a compromised backend cannot name its own contract or
|
|
51
|
+
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need no pins.
|
|
52
|
+
*/
|
|
53
|
+
callTargets?: (chain: string) => readonly string[];
|
|
54
|
+
spenders?: (chain: string) => readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* The provider's own check of a quote's transaction, run here on the client against the request
|
|
57
|
+
* the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the
|
|
58
|
+
* auction. The backend runs the same check, but the backend is what this guards against.
|
|
59
|
+
*/
|
|
60
|
+
verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;
|
|
61
|
+
/**
|
|
62
|
+
* The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built
|
|
63
|
+
* for: the reason it fails, or null. A build it refuses is never approved or signed.
|
|
64
|
+
*/
|
|
65
|
+
verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this
|
|
68
|
+
* channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).
|
|
69
|
+
*/
|
|
70
|
+
allowInsecureBaseUrl?: boolean;
|
|
46
71
|
/** Defaults to globalThis.fetch — injectable for tests. */
|
|
47
72
|
fetchImpl?: typeof fetch;
|
|
48
73
|
/** Extra headers (e.g. auth) attached to every call. */
|
package/dist/remote.js
CHANGED
package/dist/server.cjs
CHANGED
|
@@ -27,8 +27,26 @@ module.exports = __toCommonJS(server_exports);
|
|
|
27
27
|
|
|
28
28
|
// src/util.ts
|
|
29
29
|
var import_swap2_assets = require("@anywalletkit/swap2-assets");
|
|
30
|
+
var SCHEME_MAX = 32;
|
|
31
|
+
var isLetter = (c) => c >= 97 && c <= 122 || c >= 65 && c <= 90;
|
|
32
|
+
var isSchemeChar = (c) => isLetter(c) || c >= 48 && c <= 57 || c === 43 || c === 45 || c === 46;
|
|
33
|
+
var endsUrl = (c) => c === 34 || c === 39 || c === 60 || c === 62 || c >= 9 && c <= 13 || c === 32 || c === 160 || c === 5760 || c >= 8192 && c <= 8202 || c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279;
|
|
30
34
|
function redactUrls(text) {
|
|
31
|
-
|
|
35
|
+
let out = "";
|
|
36
|
+
let done = 0;
|
|
37
|
+
for (let at = text.indexOf("://"); at !== -1; at = text.indexOf("://", at + 3)) {
|
|
38
|
+
let start = at;
|
|
39
|
+
while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;
|
|
40
|
+
while (start < at && !isLetter(text.charCodeAt(start))) start++;
|
|
41
|
+
if (start === at) continue;
|
|
42
|
+
let end = at + 3;
|
|
43
|
+
while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;
|
|
44
|
+
if (end === at + 3) continue;
|
|
45
|
+
out += `${text.slice(done, start)}<url>`;
|
|
46
|
+
done = end;
|
|
47
|
+
at = end - 3;
|
|
48
|
+
}
|
|
49
|
+
return done === 0 ? text : out + text.slice(done);
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
// src/validate.ts
|
|
@@ -158,7 +176,7 @@ function createSwap2Handler(opts) {
|
|
|
158
176
|
const tokened = { ...normalized, intentId: `${normalized.intentId}~${await intentTag(providerName, subject, normalized.intentId)}` };
|
|
159
177
|
return ok(await seal(providerName, subject, tokened));
|
|
160
178
|
}
|
|
161
|
-
if (refusal) return ok({ noQuote:
|
|
179
|
+
if (refusal) return ok({ noQuote: clientText(String(refusal)) });
|
|
162
180
|
return ok(params.dry === false ? { noQuote: `${providerName} refused without naming a reason` } : null);
|
|
163
181
|
}
|
|
164
182
|
case "build":
|
|
@@ -236,13 +254,14 @@ async function refused(fn) {
|
|
|
236
254
|
return await fn();
|
|
237
255
|
} catch (err) {
|
|
238
256
|
if (err instanceof Error && /^[a-z0-9_-]+: \S/i.test(err.message)) {
|
|
239
|
-
throw new Swap2HandlerError(422,
|
|
257
|
+
throw new Swap2HandlerError(422, clientText(err.message));
|
|
240
258
|
}
|
|
241
259
|
throw err;
|
|
242
260
|
}
|
|
243
261
|
}
|
|
262
|
+
var clientText = (text) => redactUrls(text.slice(0, 2048)).slice(0, 300);
|
|
244
263
|
var ok = (body) => ({ status: 200, body: body === void 0 ? null : body });
|
|
245
|
-
var fail = (status, error) => ({ status, body: { error:
|
|
264
|
+
var fail = (status, error) => ({ status, body: { error: clientText(error) } });
|
|
246
265
|
var DIGITS = (max) => new RegExp(`^\\d{1,${max}}$`);
|
|
247
266
|
function bad(field, why) {
|
|
248
267
|
throw new Swap2HandlerError(400, `${field}: ${why}`);
|