@owney/sdk 0.7.22-beta.0 → 0.7.23-beta.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/dist/index.cjs +2111 -2619
- package/dist/index.d.cts +211 -17
- package/dist/index.d.ts +211 -17
- package/dist/index.js +1003 -248
- package/package.json +3 -5
- package/dist/chunk-5LU2SHO7.js +0 -50
- package/dist/chunk-W3FYRJLJ.js +0 -254
- package/dist/chunk-XBDJWZXY.js +0 -141
- package/dist/surfliquid.agent-XDJ672GM.js +0 -638
- package/dist/surfliquid.smart-account-DWV5B3D5.js +0 -123
package/dist/index.js
CHANGED
|
@@ -1,18 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var OwneyError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
agentId;
|
|
6
|
+
constructor(code, message, details, agentId) {
|
|
7
|
+
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
8
|
+
super(`${prefix} ${message}`);
|
|
9
|
+
this.name = "OwneyError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
this.agentId = agentId;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var AgentNotFoundError = class extends OwneyError {
|
|
16
|
+
constructor(agentId, available) {
|
|
17
|
+
super(
|
|
18
|
+
"AGENT_NOT_FOUND",
|
|
19
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
20
|
+
{ agentId, available },
|
|
21
|
+
agentId
|
|
22
|
+
);
|
|
23
|
+
this.name = "AgentNotFoundError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var NotConnectedError = class extends OwneyError {
|
|
27
|
+
constructor() {
|
|
28
|
+
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
29
|
+
this.name = "NotConnectedError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var AgentChainIncompatibleError = class extends OwneyError {
|
|
33
|
+
incompatibleAgents;
|
|
34
|
+
connectedChainId;
|
|
35
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
36
|
+
const details = incompatibleAgents.map(
|
|
37
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
38
|
+
).join("; ");
|
|
39
|
+
super(
|
|
40
|
+
"AGENT_CHAIN_INCOMPATIBLE",
|
|
41
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
42
|
+
{ incompatibleAgents, connectedChainId }
|
|
43
|
+
);
|
|
44
|
+
this.name = "AgentChainIncompatibleError";
|
|
45
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
46
|
+
this.connectedChainId = connectedChainId;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
16
49
|
|
|
17
50
|
// src/lib/rate-limit.ts
|
|
18
51
|
function rateLimitDelay(error, now = Date.now()) {
|
|
@@ -179,6 +212,23 @@ var SupportedAssets = [
|
|
|
179
212
|
}
|
|
180
213
|
];
|
|
181
214
|
|
|
215
|
+
// src/lib/debug.ts
|
|
216
|
+
var configuredDebug = false;
|
|
217
|
+
function setOwneyDebug(enabled) {
|
|
218
|
+
configuredDebug = enabled;
|
|
219
|
+
}
|
|
220
|
+
function isOwneyDebug() {
|
|
221
|
+
return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
|
|
222
|
+
}
|
|
223
|
+
function debugLog(scope, message, data) {
|
|
224
|
+
if (!isOwneyDebug()) return;
|
|
225
|
+
if (data === void 0) {
|
|
226
|
+
console.log(`[${scope}] ${message}`);
|
|
227
|
+
} else {
|
|
228
|
+
console.log(`[${scope}] ${message}`, data);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
182
232
|
// src/lib/utils.ts
|
|
183
233
|
var isValidChainId = (chainId) => {
|
|
184
234
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
|
@@ -190,12 +240,6 @@ var isValidChainId = (chainId) => {
|
|
|
190
240
|
}
|
|
191
241
|
return chainId;
|
|
192
242
|
};
|
|
193
|
-
function isSameAsset(tokenSymbol, asset) {
|
|
194
|
-
const token = tokenSymbol.toLowerCase();
|
|
195
|
-
const target = asset.toLowerCase();
|
|
196
|
-
if (token === target) return true;
|
|
197
|
-
return target === "weth" && token === "eth" || target === "eth" && token === "weth";
|
|
198
|
-
}
|
|
199
243
|
function hexToDecimal(hex, decimals = 6) {
|
|
200
244
|
const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
|
|
201
245
|
const parsed = BigInt(normalized);
|
|
@@ -1449,37 +1493,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1449
1493
|
);
|
|
1450
1494
|
}
|
|
1451
1495
|
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Records the deposit with Zyfai, retrying transient failures.
|
|
1454
|
-
*
|
|
1455
|
-
* This runs AFTER the transfer has already landed on-chain, so it must never
|
|
1456
|
-
* fail the deposit — the funds moved. But it is also the ONLY source of the
|
|
1457
|
-
* "Top up wallet" entry the history is built from: Zyfai auto-deploys the
|
|
1458
|
-
* balance it detects either way, so when this call is lost the user's deposit
|
|
1459
|
-
* never appears in Activity (an earlier withdrawal stays the newest row) and
|
|
1460
|
-
* nothing ever backfills it. Retry, then log loudly enough to be recoverable.
|
|
1461
|
-
*/
|
|
1462
|
-
async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
|
|
1463
|
-
const ATTEMPTS = 3;
|
|
1464
|
-
const RETRY_DELAY_MS = 1e3;
|
|
1465
|
-
for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
|
|
1466
|
-
try {
|
|
1467
|
-
await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
|
|
1468
|
-
return;
|
|
1469
|
-
} catch (logError) {
|
|
1470
|
-
if (attempt === ATTEMPTS) {
|
|
1471
|
-
console.error(
|
|
1472
|
-
"[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
|
|
1473
|
-
{ txHash, chainId, amount, tokenAddress, error: logError }
|
|
1474
|
-
);
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
await new Promise(
|
|
1478
|
-
(resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
1496
|
/**
|
|
1484
1497
|
* True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
|
|
1485
1498
|
* to this EOA — the "new wallet" kind that is predeployed on the chains it
|
|
@@ -1789,12 +1802,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1789
1802
|
);
|
|
1790
1803
|
const txHash = await depositCallback(smartWallet, validChainId, amount);
|
|
1791
1804
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
1792
|
-
|
|
1793
|
-
validChainId,
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1805
|
+
try {
|
|
1806
|
+
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
1807
|
+
} catch (logError) {
|
|
1808
|
+
console.warn(
|
|
1809
|
+
"[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
|
|
1810
|
+
logError
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1798
1813
|
return { txHash, smartWallet, amount };
|
|
1799
1814
|
}
|
|
1800
1815
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
@@ -1995,22 +2010,677 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1995
2010
|
await this.sdk.updateUserProfile(update);
|
|
1996
2011
|
return true;
|
|
1997
2012
|
}
|
|
1998
|
-
// --- IAgent: Discovery (no wallet required) ---
|
|
1999
|
-
async getAgentApy(days, options) {
|
|
2000
|
-
const raw = await this.sdk.getAPYPerStrategy(
|
|
2001
|
-
false,
|
|
2002
|
-
DayFilterMapping[days],
|
|
2003
|
-
"aggressive",
|
|
2004
|
-
options?.chainId,
|
|
2005
|
-
options?.tokenSymbol
|
|
2013
|
+
// --- IAgent: Discovery (no wallet required) ---
|
|
2014
|
+
async getAgentApy(days, options) {
|
|
2015
|
+
const raw = await this.sdk.getAPYPerStrategy(
|
|
2016
|
+
false,
|
|
2017
|
+
DayFilterMapping[days],
|
|
2018
|
+
"aggressive",
|
|
2019
|
+
options?.chainId,
|
|
2020
|
+
options?.tokenSymbol
|
|
2021
|
+
);
|
|
2022
|
+
return mapApyByStrategy(raw);
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
|
|
2026
|
+
// src/lib/routing-api.ts
|
|
2027
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2028
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2029
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
2030
|
+
try {
|
|
2031
|
+
const res = await fetch(url, {
|
|
2032
|
+
method: "GET",
|
|
2033
|
+
headers: {
|
|
2034
|
+
"Content-Type": "application/json",
|
|
2035
|
+
"x-owney-api-key": `${apiKey}`
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
if (!res.ok) {
|
|
2039
|
+
if (res.status !== 404) {
|
|
2040
|
+
console.warn(
|
|
2041
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
return null;
|
|
2045
|
+
}
|
|
2046
|
+
const json = await res.json();
|
|
2047
|
+
const policy = json.success ? json.data ?? null : null;
|
|
2048
|
+
debugLog(
|
|
2049
|
+
"owney-sdk",
|
|
2050
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2051
|
+
policy ?? void 0
|
|
2052
|
+
);
|
|
2053
|
+
return policy;
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
console.warn(
|
|
2056
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
2057
|
+
error instanceof Error ? error.message : String(error)
|
|
2058
|
+
);
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2063
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
2064
|
+
const res = await fetch(url, {
|
|
2065
|
+
method: "GET",
|
|
2066
|
+
headers: {
|
|
2067
|
+
"Content-Type": "application/json",
|
|
2068
|
+
"x-owney-api-key": `${apiKey}`
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
if (!res.ok) {
|
|
2072
|
+
const text = await res.text().catch(() => "");
|
|
2073
|
+
throw new OwneyError(
|
|
2074
|
+
"API_ROUTING_ERROR",
|
|
2075
|
+
`Routing API error ${res.status}: ${text}`,
|
|
2076
|
+
{ statusCode: res.status, responseBody: text }
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
const json = await res.json();
|
|
2080
|
+
if (!json.success) {
|
|
2081
|
+
throw new OwneyError(
|
|
2082
|
+
"API_ROUTING_FAILED",
|
|
2083
|
+
`Routing API request failed: ${json.message}`,
|
|
2084
|
+
{ message: json.message }
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
return json.data;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// src/lib/chain-guard.ts
|
|
2091
|
+
var CHAIN_NAMES = {
|
|
2092
|
+
1: "Ethereum",
|
|
2093
|
+
8453: "Base",
|
|
2094
|
+
42161: "Arbitrum"
|
|
2095
|
+
};
|
|
2096
|
+
function chainName(chainId) {
|
|
2097
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2098
|
+
}
|
|
2099
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2100
|
+
const actual = await pub.getChainId();
|
|
2101
|
+
if (actual === expected) return;
|
|
2102
|
+
try {
|
|
2103
|
+
await wallet.switchChain({ id: expected });
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
throw new OwneyError(
|
|
2106
|
+
"CHAIN_MISMATCH",
|
|
2107
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2108
|
+
{
|
|
2109
|
+
expectedChainId: expected,
|
|
2110
|
+
actualChainId: actual,
|
|
2111
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
const after = await pub.getChainId();
|
|
2116
|
+
if (after !== expected) {
|
|
2117
|
+
throw new OwneyError(
|
|
2118
|
+
"CHAIN_MISMATCH",
|
|
2119
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2120
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/lib/swap/swap-api.ts
|
|
2126
|
+
async function request(baseUrl, apiKey, path, init) {
|
|
2127
|
+
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
2128
|
+
const res = await fetch(url, {
|
|
2129
|
+
method: init?.method ?? "GET",
|
|
2130
|
+
headers: {
|
|
2131
|
+
"Content-Type": "application/json",
|
|
2132
|
+
"x-owney-api-key": apiKey
|
|
2133
|
+
},
|
|
2134
|
+
...init ? { body: JSON.stringify(init.body) } : {}
|
|
2135
|
+
});
|
|
2136
|
+
if (!res.ok) {
|
|
2137
|
+
const text = await res.text().catch(() => "");
|
|
2138
|
+
if (res.status === 429) {
|
|
2139
|
+
throw new OwneyError(
|
|
2140
|
+
"SWAP_RATE_LIMITED",
|
|
2141
|
+
"Swap provider is rate limiting, retry shortly",
|
|
2142
|
+
{ statusCode: res.status }
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
if (res.status === 403) {
|
|
2146
|
+
throw new OwneyError(
|
|
2147
|
+
"SWAP_DISABLED",
|
|
2148
|
+
"Swap is not enabled for this organization",
|
|
2149
|
+
{ statusCode: res.status }
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
throw new OwneyError(
|
|
2153
|
+
"SWAP_REQUEST_FAILED",
|
|
2154
|
+
`Swap API error ${res.status}: ${text}`,
|
|
2155
|
+
{ statusCode: res.status, responseBody: text }
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
const json = await res.json();
|
|
2159
|
+
if (!json.success) {
|
|
2160
|
+
throw new OwneyError(
|
|
2161
|
+
"SWAP_REQUEST_FAILED",
|
|
2162
|
+
`Swap API request failed: ${json.message ?? "unknown error"}`,
|
|
2163
|
+
{ message: json.message }
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
return json.data;
|
|
2167
|
+
}
|
|
2168
|
+
function createSwapApi(baseUrl, apiKey) {
|
|
2169
|
+
return {
|
|
2170
|
+
/** Source assets the user may pay with, and each chain's deposit targets. */
|
|
2171
|
+
listTokens: () => request(baseUrl, apiKey, "/tokens"),
|
|
2172
|
+
/**
|
|
2173
|
+
* `walletAddress` is required even though the routing API could not infer
|
|
2174
|
+
* it: the Fusion+ quoter binds a quote to whoever will sign the order and
|
|
2175
|
+
* rejects the request without it.
|
|
2176
|
+
*/
|
|
2177
|
+
quote: (params) => request(baseUrl, apiKey, "/quote", {
|
|
2178
|
+
method: "POST",
|
|
2179
|
+
body: {
|
|
2180
|
+
srcChainId: params.from.chainId,
|
|
2181
|
+
srcSymbol: params.from.symbol,
|
|
2182
|
+
dstChainId: params.to.chainId,
|
|
2183
|
+
dstSymbol: params.to.symbol,
|
|
2184
|
+
amount: params.from.amount,
|
|
2185
|
+
walletAddress: params.walletAddress
|
|
2186
|
+
}
|
|
2187
|
+
}),
|
|
2188
|
+
/** Ready-to-send calldata for a same-chain swap. */
|
|
2189
|
+
swapTx: (params) => request(baseUrl, apiKey, "/tx", {
|
|
2190
|
+
method: "POST",
|
|
2191
|
+
body: {
|
|
2192
|
+
srcChainId: params.from.chainId,
|
|
2193
|
+
srcSymbol: params.from.symbol,
|
|
2194
|
+
dstChainId: params.to.chainId,
|
|
2195
|
+
dstSymbol: params.to.symbol,
|
|
2196
|
+
amount: params.from.amount,
|
|
2197
|
+
walletAddress: params.walletAddress,
|
|
2198
|
+
slippage: params.slippage
|
|
2199
|
+
}
|
|
2200
|
+
}),
|
|
2201
|
+
/**
|
|
2202
|
+
* Builds a Fusion+ order server-side and returns EIP-712 typed data.
|
|
2203
|
+
*
|
|
2204
|
+
* Only HASHES go over the wire. The preimages never leave the browser —
|
|
2205
|
+
* see swap.secrets.
|
|
2206
|
+
*/
|
|
2207
|
+
buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
|
|
2208
|
+
method: "POST",
|
|
2209
|
+
body: {
|
|
2210
|
+
srcChainId: params.from.chainId,
|
|
2211
|
+
srcSymbol: params.from.symbol,
|
|
2212
|
+
dstChainId: params.to.chainId,
|
|
2213
|
+
dstSymbol: params.to.symbol,
|
|
2214
|
+
amount: params.from.amount,
|
|
2215
|
+
walletAddress: params.walletAddress,
|
|
2216
|
+
secretHashes: params.secretHashes,
|
|
2217
|
+
...params.receiver ? { receiver: params.receiver } : {}
|
|
2218
|
+
}
|
|
2219
|
+
}),
|
|
2220
|
+
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
2221
|
+
/**
|
|
2222
|
+
* Only call once `readyForSecrets` reports the escrow deployed. Publishing
|
|
2223
|
+
* earlier hands a resolver the preimage while the user's funds are locked
|
|
2224
|
+
* and nothing has been posted on the destination chain.
|
|
2225
|
+
*/
|
|
2226
|
+
submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
|
|
2227
|
+
method: "POST",
|
|
2228
|
+
body: { orderHash, secret }
|
|
2229
|
+
}),
|
|
2230
|
+
orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
|
|
2231
|
+
readyForSecrets: (orderHash) => request(
|
|
2232
|
+
baseUrl,
|
|
2233
|
+
apiKey,
|
|
2234
|
+
`/order/${orderHash}/ready-for-secrets`
|
|
2235
|
+
)
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// src/lib/swap/swap.rpc.ts
|
|
2240
|
+
import { fallback, http as http2 } from "viem";
|
|
2241
|
+
var DEFAULT_RPC_URLS = {
|
|
2242
|
+
1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
|
|
2243
|
+
8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
|
|
2244
|
+
42161: [
|
|
2245
|
+
"https://arb1.arbitrum.io/rpc",
|
|
2246
|
+
"https://arbitrum-one-rpc.publicnode.com"
|
|
2247
|
+
]
|
|
2248
|
+
};
|
|
2249
|
+
function swapReadTransport(chainId, overrides) {
|
|
2250
|
+
const override = overrides?.[chainId];
|
|
2251
|
+
if (override) return http2(override);
|
|
2252
|
+
const urls = DEFAULT_RPC_URLS[chainId];
|
|
2253
|
+
if (!urls || urls.length === 0) return http2();
|
|
2254
|
+
return fallback(urls.map((url) => http2(url)));
|
|
2255
|
+
}
|
|
2256
|
+
function receiptTimeoutMs(chainId) {
|
|
2257
|
+
return chainId === 1 ? 6e5 : 18e4;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/lib/permit2.ts
|
|
2261
|
+
import { bytesToHex } from "viem";
|
|
2262
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2263
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2264
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
2265
|
+
{
|
|
2266
|
+
type: "function",
|
|
2267
|
+
name: "allowance",
|
|
2268
|
+
stateMutability: "view",
|
|
2269
|
+
inputs: [
|
|
2270
|
+
{ name: "owner", type: "address" },
|
|
2271
|
+
{ name: "spender", type: "address" }
|
|
2272
|
+
],
|
|
2273
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
type: "function",
|
|
2277
|
+
name: "approve",
|
|
2278
|
+
stateMutability: "nonpayable",
|
|
2279
|
+
inputs: [
|
|
2280
|
+
{ name: "spender", type: "address" },
|
|
2281
|
+
{ name: "amount", type: "uint256" }
|
|
2282
|
+
],
|
|
2283
|
+
outputs: [{ name: "", type: "bool" }]
|
|
2284
|
+
},
|
|
2285
|
+
{
|
|
2286
|
+
type: "function",
|
|
2287
|
+
name: "balanceOf",
|
|
2288
|
+
stateMutability: "view",
|
|
2289
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2290
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2291
|
+
}
|
|
2292
|
+
];
|
|
2293
|
+
function buildPermitTransferFromTypedData(input) {
|
|
2294
|
+
return {
|
|
2295
|
+
domain: {
|
|
2296
|
+
name: "Permit2",
|
|
2297
|
+
chainId: input.chainId,
|
|
2298
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
2299
|
+
},
|
|
2300
|
+
types: {
|
|
2301
|
+
PermitTransferFrom: [
|
|
2302
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
2303
|
+
{ name: "spender", type: "address" },
|
|
2304
|
+
{ name: "nonce", type: "uint256" },
|
|
2305
|
+
{ name: "deadline", type: "uint256" }
|
|
2306
|
+
],
|
|
2307
|
+
TokenPermissions: [
|
|
2308
|
+
{ name: "token", type: "address" },
|
|
2309
|
+
{ name: "amount", type: "uint256" }
|
|
2310
|
+
]
|
|
2311
|
+
},
|
|
2312
|
+
primaryType: "PermitTransferFrom",
|
|
2313
|
+
message: input.message
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
function randomPermit2Nonce() {
|
|
2317
|
+
const bytes = new Uint8Array(32);
|
|
2318
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2319
|
+
return BigInt(bytesToHex(bytes));
|
|
2320
|
+
}
|
|
2321
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2322
|
+
return publicClient.readContract({
|
|
2323
|
+
address: token,
|
|
2324
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2325
|
+
functionName: "allowance",
|
|
2326
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2330
|
+
return publicClient.readContract({
|
|
2331
|
+
address: token,
|
|
2332
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2333
|
+
functionName: "balanceOf",
|
|
2334
|
+
args: [owner]
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
// src/lib/swap/swap.secrets.ts
|
|
2339
|
+
import { keccak256, toHex } from "viem";
|
|
2340
|
+
var SECRET_BYTES = 32;
|
|
2341
|
+
function randomBytes(length) {
|
|
2342
|
+
const bytes = new Uint8Array(length);
|
|
2343
|
+
const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
2344
|
+
if (!cryptoObj?.getRandomValues) {
|
|
2345
|
+
throw new Error(
|
|
2346
|
+
"[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
cryptoObj.getRandomValues(bytes);
|
|
2350
|
+
return bytes;
|
|
2351
|
+
}
|
|
2352
|
+
function mintSecrets(count) {
|
|
2353
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
2354
|
+
throw new Error(
|
|
2355
|
+
`[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
const secrets = [];
|
|
2359
|
+
const secretHashes = [];
|
|
2360
|
+
for (let i = 0; i < count; i++) {
|
|
2361
|
+
const secret = toHex(randomBytes(SECRET_BYTES));
|
|
2362
|
+
secrets.push(secret);
|
|
2363
|
+
secretHashes.push(keccak256(secret));
|
|
2364
|
+
}
|
|
2365
|
+
return { secrets, secretHashes };
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// src/lib/swap/swap.types.ts
|
|
2369
|
+
var SWAP_TERMINAL_STATUSES = [
|
|
2370
|
+
"executed",
|
|
2371
|
+
"expired",
|
|
2372
|
+
"cancelled",
|
|
2373
|
+
"refunded"
|
|
2374
|
+
];
|
|
2375
|
+
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2376
|
+
|
|
2377
|
+
// src/lib/swap/swap.order-runner.ts
|
|
2378
|
+
var DEFAULT_POLL_MS = 5e3;
|
|
2379
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
2380
|
+
var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
2381
|
+
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
2382
|
+
async function runFusionOrder(deps, options) {
|
|
2383
|
+
const {
|
|
2384
|
+
orderHash,
|
|
2385
|
+
secrets,
|
|
2386
|
+
onStage,
|
|
2387
|
+
pollIntervalMs = DEFAULT_POLL_MS,
|
|
2388
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
2389
|
+
} = options;
|
|
2390
|
+
const deadline = deps.now() + timeoutMs;
|
|
2391
|
+
let failures = 0;
|
|
2392
|
+
const published = /* @__PURE__ */ new Set();
|
|
2393
|
+
onStage?.("swapping");
|
|
2394
|
+
for (; ; ) {
|
|
2395
|
+
if (deps.now() >= deadline) {
|
|
2396
|
+
throw new OwneyError(
|
|
2397
|
+
"SWAP_REQUEST_FAILED",
|
|
2398
|
+
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
2399
|
+
{ orderHash }
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
let ready;
|
|
2403
|
+
try {
|
|
2404
|
+
ready = await deps.readyForSecrets(orderHash);
|
|
2405
|
+
} catch {
|
|
2406
|
+
ready = {};
|
|
2407
|
+
}
|
|
2408
|
+
for (const fill of ready.fills ?? []) {
|
|
2409
|
+
if (published.has(fill.idx)) continue;
|
|
2410
|
+
const secret = secrets[fill.idx];
|
|
2411
|
+
if (secret === void 0) {
|
|
2412
|
+
throw new OwneyError(
|
|
2413
|
+
"SWAP_REQUEST_FAILED",
|
|
2414
|
+
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
2415
|
+
{ orderHash, fillIndex: fill.idx }
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
try {
|
|
2419
|
+
await deps.submitSecret(orderHash, secret);
|
|
2420
|
+
published.add(fill.idx);
|
|
2421
|
+
} catch {
|
|
2422
|
+
failures += 1;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
let status;
|
|
2426
|
+
try {
|
|
2427
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2428
|
+
failures = 0;
|
|
2429
|
+
} catch {
|
|
2430
|
+
failures += 1;
|
|
2431
|
+
await deps.sleep(backoffFor(failures, pollIntervalMs));
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
if (status === "refunding") onStage?.("refunding");
|
|
2435
|
+
if (isSwapTerminal(status)) {
|
|
2436
|
+
if (status === "executed") {
|
|
2437
|
+
onStage?.("swapped");
|
|
2438
|
+
return { status, filled: true };
|
|
2439
|
+
}
|
|
2440
|
+
if (status === "refunded") onStage?.("refunded");
|
|
2441
|
+
throw new OwneyError(
|
|
2442
|
+
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2443
|
+
status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
|
|
2444
|
+
{ orderHash, status }
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
await deps.sleep(pollIntervalMs);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// src/lib/swap/swap.secret-store.ts
|
|
2452
|
+
var KEY_PREFIX2 = "owney.swap.order";
|
|
2453
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
2454
|
+
var storage2 = () => {
|
|
2455
|
+
if (typeof window === "undefined") return null;
|
|
2456
|
+
try {
|
|
2457
|
+
return window.localStorage;
|
|
2458
|
+
} catch {
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
|
|
2463
|
+
function saveOrder(order) {
|
|
2464
|
+
const store = storage2();
|
|
2465
|
+
if (!store) return;
|
|
2466
|
+
try {
|
|
2467
|
+
store.setItem(keyFor(order.orderHash), JSON.stringify(order));
|
|
2468
|
+
} catch {
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
function clearOrder(orderHash) {
|
|
2472
|
+
const store = storage2();
|
|
2473
|
+
if (!store) return;
|
|
2474
|
+
try {
|
|
2475
|
+
store.removeItem(keyFor(orderHash));
|
|
2476
|
+
} catch {
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function listOrders(now = Date.now()) {
|
|
2480
|
+
const store = storage2();
|
|
2481
|
+
if (!store) return [];
|
|
2482
|
+
const out = [];
|
|
2483
|
+
try {
|
|
2484
|
+
const keys = [];
|
|
2485
|
+
for (let i = 0; i < store.length; i++) {
|
|
2486
|
+
const key2 = store.key(i);
|
|
2487
|
+
if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
|
|
2488
|
+
}
|
|
2489
|
+
for (const key2 of keys) {
|
|
2490
|
+
const raw = store.getItem(key2);
|
|
2491
|
+
if (!raw) continue;
|
|
2492
|
+
try {
|
|
2493
|
+
const parsed = JSON.parse(raw);
|
|
2494
|
+
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
2495
|
+
store.removeItem(key2);
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2498
|
+
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
2499
|
+
out.push(parsed);
|
|
2500
|
+
}
|
|
2501
|
+
} catch {
|
|
2502
|
+
store.removeItem(key2);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
} catch {
|
|
2506
|
+
return out;
|
|
2507
|
+
}
|
|
2508
|
+
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/lib/swap/swap.executor.ts
|
|
2512
|
+
var DEFAULT_SLIPPAGE = 1;
|
|
2513
|
+
async function affordableAmount(deps, quoted) {
|
|
2514
|
+
const balance = await deps.readSourceBalance();
|
|
2515
|
+
if (balance >= quoted) return quoted;
|
|
2516
|
+
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
2517
|
+
quoted: quoted.toString(),
|
|
2518
|
+
balance: balance.toString(),
|
|
2519
|
+
short: (quoted - balance).toString()
|
|
2520
|
+
});
|
|
2521
|
+
return balance;
|
|
2522
|
+
}
|
|
2523
|
+
async function executeSwap(deps, options) {
|
|
2524
|
+
const { quote, walletAddress, onStage } = options;
|
|
2525
|
+
debugLog("owney-sdk", "swap: start", {
|
|
2526
|
+
rail: quote.rail,
|
|
2527
|
+
from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
|
|
2528
|
+
to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
|
|
2529
|
+
expected: quote.dst.amount,
|
|
2530
|
+
floor: quote.dstAmountMin
|
|
2531
|
+
});
|
|
2532
|
+
const before = await deps.readTargetBalance();
|
|
2533
|
+
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2534
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2535
|
+
const after = await deps.readTargetBalance();
|
|
2536
|
+
const received = after - before;
|
|
2537
|
+
debugLog("owney-sdk", "swap: target balance after", {
|
|
2538
|
+
after: after.toString(),
|
|
2539
|
+
received: received.toString()
|
|
2540
|
+
});
|
|
2541
|
+
if (received <= 0n) {
|
|
2542
|
+
throw new OwneyError(
|
|
2543
|
+
"SWAP_REQUEST_FAILED",
|
|
2544
|
+
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
2545
|
+
{ rail: quote.rail, ...result }
|
|
2006
2546
|
);
|
|
2007
|
-
return mapApyByStrategy(raw);
|
|
2008
2547
|
}
|
|
2009
|
-
};
|
|
2548
|
+
return { received: received.toString(), ...result };
|
|
2549
|
+
}
|
|
2550
|
+
async function runClassic(deps, options) {
|
|
2551
|
+
const {
|
|
2552
|
+
quote,
|
|
2553
|
+
walletAddress,
|
|
2554
|
+
slippage = DEFAULT_SLIPPAGE,
|
|
2555
|
+
onStage
|
|
2556
|
+
} = options;
|
|
2557
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2558
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2559
|
+
onStage?.("quoting");
|
|
2560
|
+
debugLog("owney-sdk", "swap: fetching classic calldata");
|
|
2561
|
+
const { tx } = await deps.api.swapTx({
|
|
2562
|
+
from: {
|
|
2563
|
+
chainId: quote.src.chainId,
|
|
2564
|
+
symbol: quote.src.symbol,
|
|
2565
|
+
amount: amount.toString()
|
|
2566
|
+
},
|
|
2567
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2568
|
+
walletAddress,
|
|
2569
|
+
slippage
|
|
2570
|
+
});
|
|
2571
|
+
const isNative = BigInt(tx.value ?? "0") > 0n;
|
|
2572
|
+
if (!isNative) {
|
|
2573
|
+
const needed = amount;
|
|
2574
|
+
const current = await deps.readAllowance(tx.to);
|
|
2575
|
+
debugLog("owney-sdk", "swap: allowance", {
|
|
2576
|
+
spender: tx.to,
|
|
2577
|
+
current: current.toString(),
|
|
2578
|
+
needed: needed.toString()
|
|
2579
|
+
});
|
|
2580
|
+
if (current < needed) {
|
|
2581
|
+
onStage?.("approving");
|
|
2582
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2583
|
+
await deps.approve(tx.to, MAX_UINT256);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
onStage?.("signing");
|
|
2587
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2588
|
+
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
2589
|
+
const txHash = await deps.sendTransaction({
|
|
2590
|
+
to: tx.to,
|
|
2591
|
+
data: tx.data,
|
|
2592
|
+
value: tx.value ?? "0"
|
|
2593
|
+
});
|
|
2594
|
+
onStage?.("swapped");
|
|
2595
|
+
return { txHash };
|
|
2596
|
+
}
|
|
2597
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2598
|
+
const { quote, onStage } = options;
|
|
2599
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2600
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2601
|
+
if (quote.spender && !isNativeSource) {
|
|
2602
|
+
const needed = amount;
|
|
2603
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2604
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2605
|
+
spender: quote.spender,
|
|
2606
|
+
current: current.toString(),
|
|
2607
|
+
needed: needed.toString()
|
|
2608
|
+
});
|
|
2609
|
+
if (current < needed) {
|
|
2610
|
+
onStage?.("approving");
|
|
2611
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2612
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2613
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2617
|
+
onStage?.("quoting");
|
|
2618
|
+
debugLog("owney-sdk", "swap: building fusion order", {
|
|
2619
|
+
secrets: secretHashes.length
|
|
2620
|
+
});
|
|
2621
|
+
const built = await deps.api.buildOrder({
|
|
2622
|
+
from: {
|
|
2623
|
+
chainId: quote.src.chainId,
|
|
2624
|
+
symbol: quote.src.symbol,
|
|
2625
|
+
// The trimmed amount — the order is re-quoted at this size server-side.
|
|
2626
|
+
amount: amount.toString()
|
|
2627
|
+
},
|
|
2628
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2629
|
+
walletAddress,
|
|
2630
|
+
secretHashes
|
|
2631
|
+
});
|
|
2632
|
+
saveOrder({
|
|
2633
|
+
orderHash: built.orderHash,
|
|
2634
|
+
secrets,
|
|
2635
|
+
srcChainId: quote.src.chainId,
|
|
2636
|
+
srcSymbol: quote.src.symbol,
|
|
2637
|
+
dstChainId: quote.dst.chainId,
|
|
2638
|
+
dstSymbol: quote.dst.symbol,
|
|
2639
|
+
amount: amount.toString(),
|
|
2640
|
+
createdAt: Date.now()
|
|
2641
|
+
});
|
|
2642
|
+
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
2643
|
+
onStage?.("signing");
|
|
2644
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2645
|
+
debugLog("owney-sdk", "swap: awaiting signature in wallet", {
|
|
2646
|
+
signingOnChain: quote.src.chainId
|
|
2647
|
+
});
|
|
2648
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2649
|
+
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
2650
|
+
await deps.api.submitOrder({
|
|
2651
|
+
srcChainId: quote.src.chainId,
|
|
2652
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
2653
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
2654
|
+
order: built.order,
|
|
2655
|
+
signature,
|
|
2656
|
+
quoteId: built.quoteId,
|
|
2657
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
2658
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
2659
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
2660
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
2661
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
2662
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2663
|
+
});
|
|
2664
|
+
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
2665
|
+
try {
|
|
2666
|
+
await runFusionOrder(deps.runner, {
|
|
2667
|
+
orderHash: built.orderHash,
|
|
2668
|
+
secrets,
|
|
2669
|
+
...onStage ? { onStage } : {}
|
|
2670
|
+
});
|
|
2671
|
+
} catch (error) {
|
|
2672
|
+
if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
|
|
2673
|
+
clearOrder(built.orderHash);
|
|
2674
|
+
}
|
|
2675
|
+
throw error;
|
|
2676
|
+
}
|
|
2677
|
+
clearOrder(built.orderHash);
|
|
2678
|
+
return { orderHash: built.orderHash };
|
|
2679
|
+
}
|
|
2010
2680
|
|
|
2011
2681
|
// src/lib/health-report.ts
|
|
2012
|
-
var
|
|
2013
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl =
|
|
2682
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2683
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
2014
2684
|
try {
|
|
2015
2685
|
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
2016
2686
|
method: "POST",
|
|
@@ -2044,10 +2714,11 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
2044
2714
|
// src/lib/helpers/withdraw-helper.ts
|
|
2045
2715
|
import { parseUnits } from "viem";
|
|
2046
2716
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
2717
|
+
const target = asset.toUpperCase();
|
|
2047
2718
|
return agents.map((agent) => {
|
|
2048
2719
|
const agentBalance = aggregated[agent.id];
|
|
2049
2720
|
const tokenBalance = agentBalance?.tokens.find(
|
|
2050
|
-
(t) => t.chainId === chainId &&
|
|
2721
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2051
2722
|
);
|
|
2052
2723
|
if (!tokenBalance) return { agent, balance: 0n };
|
|
2053
2724
|
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
@@ -2200,14 +2871,51 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
2200
2871
|
import {
|
|
2201
2872
|
createPublicClient as createPublicClient2,
|
|
2202
2873
|
createWalletClient,
|
|
2203
|
-
custom
|
|
2874
|
+
custom,
|
|
2875
|
+
erc20Abi as erc20Abi2
|
|
2204
2876
|
} from "viem";
|
|
2205
2877
|
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2206
2878
|
|
|
2879
|
+
// src/lib/transfer-auth.ts
|
|
2880
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2881
|
+
var ERC20_META_ABI = [
|
|
2882
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2883
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2884
|
+
];
|
|
2885
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
2886
|
+
return {
|
|
2887
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2888
|
+
types: {
|
|
2889
|
+
TransferWithAuthorization: [
|
|
2890
|
+
{ name: "from", type: "address" },
|
|
2891
|
+
{ name: "to", type: "address" },
|
|
2892
|
+
{ name: "value", type: "uint256" },
|
|
2893
|
+
{ name: "validAfter", type: "uint256" },
|
|
2894
|
+
{ name: "validBefore", type: "uint256" },
|
|
2895
|
+
{ name: "nonce", type: "bytes32" }
|
|
2896
|
+
]
|
|
2897
|
+
},
|
|
2898
|
+
primaryType: "TransferWithAuthorization",
|
|
2899
|
+
message: input.message
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
async function readTokenMeta(publicClient, token) {
|
|
2903
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
2904
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2905
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2906
|
+
]);
|
|
2907
|
+
return { tokenName, tokenVersion };
|
|
2908
|
+
}
|
|
2909
|
+
function randomAuthNonce() {
|
|
2910
|
+
const bytes = new Uint8Array(32);
|
|
2911
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2912
|
+
return bytesToHex2(bytes);
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2207
2915
|
// src/lib/sponsor-client.ts
|
|
2208
|
-
var
|
|
2916
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2209
2917
|
async function postSponsorTransferAuth(input) {
|
|
2210
|
-
const base3 = input.baseUrl ??
|
|
2918
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2211
2919
|
let res;
|
|
2212
2920
|
try {
|
|
2213
2921
|
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
@@ -2247,7 +2955,7 @@ async function postSponsorTransferAuth(input) {
|
|
|
2247
2955
|
return parsed.data;
|
|
2248
2956
|
}
|
|
2249
2957
|
async function postSponsorPermit2Transfer(input) {
|
|
2250
|
-
const base3 = input.baseUrl ??
|
|
2958
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2251
2959
|
let res;
|
|
2252
2960
|
try {
|
|
2253
2961
|
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
@@ -2285,7 +2993,7 @@ async function postSponsorPermit2Transfer(input) {
|
|
|
2285
2993
|
return parsed.data;
|
|
2286
2994
|
}
|
|
2287
2995
|
async function getSponsorRelayerAddress(input) {
|
|
2288
|
-
const base3 = input.baseUrl ??
|
|
2996
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2289
2997
|
let res;
|
|
2290
2998
|
try {
|
|
2291
2999
|
res = await fetch(
|
|
@@ -2321,119 +3029,6 @@ async function getSponsorRelayerAddress(input) {
|
|
|
2321
3029
|
return parsed.data.relayer;
|
|
2322
3030
|
}
|
|
2323
3031
|
|
|
2324
|
-
// src/lib/permit2.ts
|
|
2325
|
-
import { bytesToHex } from "viem";
|
|
2326
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2327
|
-
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2328
|
-
var ERC20_ALLOWANCE_ABI = [
|
|
2329
|
-
{
|
|
2330
|
-
type: "function",
|
|
2331
|
-
name: "allowance",
|
|
2332
|
-
stateMutability: "view",
|
|
2333
|
-
inputs: [
|
|
2334
|
-
{ name: "owner", type: "address" },
|
|
2335
|
-
{ name: "spender", type: "address" }
|
|
2336
|
-
],
|
|
2337
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2338
|
-
},
|
|
2339
|
-
{
|
|
2340
|
-
type: "function",
|
|
2341
|
-
name: "approve",
|
|
2342
|
-
stateMutability: "nonpayable",
|
|
2343
|
-
inputs: [
|
|
2344
|
-
{ name: "spender", type: "address" },
|
|
2345
|
-
{ name: "amount", type: "uint256" }
|
|
2346
|
-
],
|
|
2347
|
-
outputs: [{ name: "", type: "bool" }]
|
|
2348
|
-
},
|
|
2349
|
-
{
|
|
2350
|
-
type: "function",
|
|
2351
|
-
name: "balanceOf",
|
|
2352
|
-
stateMutability: "view",
|
|
2353
|
-
inputs: [{ name: "account", type: "address" }],
|
|
2354
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2355
|
-
}
|
|
2356
|
-
];
|
|
2357
|
-
function buildPermitTransferFromTypedData(input) {
|
|
2358
|
-
return {
|
|
2359
|
-
domain: {
|
|
2360
|
-
name: "Permit2",
|
|
2361
|
-
chainId: input.chainId,
|
|
2362
|
-
verifyingContract: PERMIT2_ADDRESS
|
|
2363
|
-
},
|
|
2364
|
-
types: {
|
|
2365
|
-
PermitTransferFrom: [
|
|
2366
|
-
{ name: "permitted", type: "TokenPermissions" },
|
|
2367
|
-
{ name: "spender", type: "address" },
|
|
2368
|
-
{ name: "nonce", type: "uint256" },
|
|
2369
|
-
{ name: "deadline", type: "uint256" }
|
|
2370
|
-
],
|
|
2371
|
-
TokenPermissions: [
|
|
2372
|
-
{ name: "token", type: "address" },
|
|
2373
|
-
{ name: "amount", type: "uint256" }
|
|
2374
|
-
]
|
|
2375
|
-
},
|
|
2376
|
-
primaryType: "PermitTransferFrom",
|
|
2377
|
-
message: input.message
|
|
2378
|
-
};
|
|
2379
|
-
}
|
|
2380
|
-
function randomPermit2Nonce() {
|
|
2381
|
-
const bytes = new Uint8Array(32);
|
|
2382
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2383
|
-
return BigInt(bytesToHex(bytes));
|
|
2384
|
-
}
|
|
2385
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2386
|
-
return publicClient.readContract({
|
|
2387
|
-
address: token,
|
|
2388
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2389
|
-
functionName: "allowance",
|
|
2390
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
async function readErc20Balance(publicClient, token, owner) {
|
|
2394
|
-
return publicClient.readContract({
|
|
2395
|
-
address: token,
|
|
2396
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2397
|
-
functionName: "balanceOf",
|
|
2398
|
-
args: [owner]
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
// src/lib/chain-guard.ts
|
|
2403
|
-
var CHAIN_NAMES = {
|
|
2404
|
-
1: "Ethereum",
|
|
2405
|
-
8453: "Base",
|
|
2406
|
-
42161: "Arbitrum"
|
|
2407
|
-
};
|
|
2408
|
-
function chainName(chainId) {
|
|
2409
|
-
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2410
|
-
}
|
|
2411
|
-
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2412
|
-
const actual = await pub.getChainId();
|
|
2413
|
-
if (actual === expected) return;
|
|
2414
|
-
try {
|
|
2415
|
-
await wallet.switchChain({ id: expected });
|
|
2416
|
-
} catch (error) {
|
|
2417
|
-
throw new OwneyError(
|
|
2418
|
-
"CHAIN_MISMATCH",
|
|
2419
|
-
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2420
|
-
{
|
|
2421
|
-
expectedChainId: expected,
|
|
2422
|
-
actualChainId: actual,
|
|
2423
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2424
|
-
}
|
|
2425
|
-
);
|
|
2426
|
-
}
|
|
2427
|
-
const after = await pub.getChainId();
|
|
2428
|
-
if (after !== expected) {
|
|
2429
|
-
throw new OwneyError(
|
|
2430
|
-
"CHAIN_MISMATCH",
|
|
2431
|
-
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2432
|
-
{ expectedChainId: expected, actualChainId: after }
|
|
2433
|
-
);
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
3032
|
// src/lib/sponsored-deposit.ts
|
|
2438
3033
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2439
3034
|
function makeSponsoredDepositCallback(deps) {
|
|
@@ -2596,7 +3191,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2596
3191
|
}
|
|
2597
3192
|
|
|
2598
3193
|
// src/lib/sponsored-calls-deposit.ts
|
|
2599
|
-
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
3194
|
+
import { encodeFunctionData, erc20Abi, toHex as toHex2 } from "viem";
|
|
2600
3195
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2601
3196
|
var DEFAULT_MAX_POLLS = 30;
|
|
2602
3197
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2604,7 +3199,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
2604
3199
|
method: "wallet_getCapabilities",
|
|
2605
3200
|
params: [owner]
|
|
2606
3201
|
});
|
|
2607
|
-
const forChain = caps?.[
|
|
3202
|
+
const forChain = caps?.[toHex2(chainId)] ?? caps?.[String(chainId)];
|
|
2608
3203
|
return Boolean(forChain?.paymasterService?.supported);
|
|
2609
3204
|
}
|
|
2610
3205
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -2649,7 +3244,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2649
3244
|
{
|
|
2650
3245
|
version: "2.0.0",
|
|
2651
3246
|
from: deps.ownerAddress,
|
|
2652
|
-
chainId:
|
|
3247
|
+
chainId: toHex2(chainId),
|
|
2653
3248
|
atomicRequired: false,
|
|
2654
3249
|
calls: [{ to: token, value: "0x0", data }],
|
|
2655
3250
|
capabilities: {
|
|
@@ -2725,7 +3320,6 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2725
3320
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2726
3321
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2727
3322
|
}
|
|
2728
|
-
var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai"];
|
|
2729
3323
|
var OwneySDK = class {
|
|
2730
3324
|
agents = /* @__PURE__ */ new Map();
|
|
2731
3325
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -3009,7 +3603,7 @@ var OwneySDK = class {
|
|
|
3009
3603
|
);
|
|
3010
3604
|
this.disabledAgents.clear();
|
|
3011
3605
|
for (const { key: key2, agent_type, is_enabled } of agentKeys) {
|
|
3012
|
-
const agent =
|
|
3606
|
+
const agent = this.createAgent(agent_type, key2);
|
|
3013
3607
|
if (!agent) continue;
|
|
3014
3608
|
this.agents.set(agent_type, agent);
|
|
3015
3609
|
if (is_enabled === false) {
|
|
@@ -3030,18 +3624,10 @@ var OwneySDK = class {
|
|
|
3030
3624
|
this.initializingAgentsPromise = null;
|
|
3031
3625
|
}
|
|
3032
3626
|
}
|
|
3033
|
-
|
|
3627
|
+
createAgent(agentId, key2) {
|
|
3034
3628
|
if (agentId === "zyfai") {
|
|
3035
3629
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
3036
3630
|
}
|
|
3037
|
-
if (agentId === "surfliquid") {
|
|
3038
|
-
if (!key2) return null;
|
|
3039
|
-
const { SurfLiquidAgent } = await import("./surfliquid.agent-XDJ672GM.js");
|
|
3040
|
-
return new SurfLiquidAgent({
|
|
3041
|
-
apiKey: this.apiKey,
|
|
3042
|
-
routingApiBaseUrl: this.routingApiBaseUrl
|
|
3043
|
-
});
|
|
3044
|
-
}
|
|
3045
3631
|
return null;
|
|
3046
3632
|
}
|
|
3047
3633
|
/**
|
|
@@ -3439,14 +4025,15 @@ var OwneySDK = class {
|
|
|
3439
4025
|
async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
|
|
3440
4026
|
try {
|
|
3441
4027
|
const balance = await agent.getBalances(state, chainId);
|
|
4028
|
+
const target = asset.toLowerCase();
|
|
3442
4029
|
const token = balance.tokens.find(
|
|
3443
|
-
(t) => t.chainId === chainId &&
|
|
4030
|
+
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3444
4031
|
);
|
|
3445
4032
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
3446
4033
|
const position = (balance.positions ?? []).find((p) => {
|
|
3447
4034
|
const positionChain = p.chain.trim().toUpperCase();
|
|
3448
4035
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
3449
|
-
return matchesChain &&
|
|
4036
|
+
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
3450
4037
|
});
|
|
3451
4038
|
return !!token && Number(token.amount) > 0 || !!position;
|
|
3452
4039
|
} catch (error) {
|
|
@@ -3493,35 +4080,212 @@ var OwneySDK = class {
|
|
|
3493
4080
|
}
|
|
3494
4081
|
return eligible;
|
|
3495
4082
|
}
|
|
4083
|
+
// --- Fund operations ---
|
|
4084
|
+
// --- Swap to yield (ROUT-242) ---
|
|
4085
|
+
/** Lazily built so an app that never swaps pays nothing for it. */
|
|
4086
|
+
swapApiClient;
|
|
4087
|
+
swapApi() {
|
|
4088
|
+
this.swapApiClient ??= createSwapApi(
|
|
4089
|
+
this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
|
|
4090
|
+
this.apiKey
|
|
4091
|
+
);
|
|
4092
|
+
return this.swapApiClient;
|
|
4093
|
+
}
|
|
3496
4094
|
/**
|
|
3497
|
-
*
|
|
3498
|
-
* chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
|
|
3499
|
-
* surfliquid first). Returns `[]` when the org has no compatible agent — never
|
|
3500
|
-
* throws on an empty org. Loads agent keys on first call (apiKey only, no
|
|
3501
|
-
* wallet), so the UI can resolve which agent to use before the user connects.
|
|
4095
|
+
* Put the wallet on `chainId`, or fail with something actionable.
|
|
3502
4096
|
*
|
|
3503
|
-
*
|
|
3504
|
-
*
|
|
4097
|
+
* Reuses the same guard the deposit rail uses, which re-reads the chain after
|
|
4098
|
+
* switching — some wallets resolve wallet_switchEthereumChain before the
|
|
4099
|
+
* network has actually changed.
|
|
3505
4100
|
*/
|
|
3506
|
-
async
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
return [...this.agents.values()].filter((agent) => {
|
|
3516
|
-
const chainAssets = agent.supportedAssets.find(
|
|
3517
|
-
(sa) => sa.chainId === chainId
|
|
4101
|
+
async ensureSwapChain(chainId) {
|
|
4102
|
+
const provider = this.requireConnectedProvider();
|
|
4103
|
+
const state = this.requireState();
|
|
4104
|
+
const chain = VIEM_CHAIN2[chainId];
|
|
4105
|
+
if (!chain) {
|
|
4106
|
+
throw new OwneyError(
|
|
4107
|
+
"CHAIN_UNSUPPORTED",
|
|
4108
|
+
`Chain ${chainId} is not supported`,
|
|
4109
|
+
{ chainId }
|
|
3518
4110
|
);
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
(
|
|
4111
|
+
}
|
|
4112
|
+
await ensureWalletOnChain(
|
|
4113
|
+
createPublicClient2({ chain, transport: custom(provider) }),
|
|
4114
|
+
createWalletClient({
|
|
4115
|
+
account: state.walletAddress,
|
|
4116
|
+
chain,
|
|
4117
|
+
transport: custom(provider)
|
|
4118
|
+
}),
|
|
4119
|
+
chainId
|
|
3522
4120
|
);
|
|
3523
4121
|
}
|
|
3524
|
-
|
|
4122
|
+
/**
|
|
4123
|
+
* Binds the executor's abstract deps to this client's wallet.
|
|
4124
|
+
*
|
|
4125
|
+
* Kept as a builder rather than baked into the executor so the whole swap
|
|
4126
|
+
* flow stays testable without a provider — the executor never imports viem.
|
|
4127
|
+
*/
|
|
4128
|
+
buildSwapDeps(quote) {
|
|
4129
|
+
const state = this.requireState();
|
|
4130
|
+
const provider = this.requireConnectedProvider();
|
|
4131
|
+
const srcChain = VIEM_CHAIN2[quote.src.chainId];
|
|
4132
|
+
const dstChain = VIEM_CHAIN2[quote.dst.chainId];
|
|
4133
|
+
const wallet = createWalletClient({
|
|
4134
|
+
account: state.walletAddress,
|
|
4135
|
+
chain: srcChain,
|
|
4136
|
+
transport: custom(provider)
|
|
4137
|
+
});
|
|
4138
|
+
const srcPublic = createPublicClient2({
|
|
4139
|
+
chain: srcChain,
|
|
4140
|
+
transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
|
|
4141
|
+
});
|
|
4142
|
+
const dstPublic = createPublicClient2({
|
|
4143
|
+
chain: dstChain,
|
|
4144
|
+
transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
|
|
4145
|
+
});
|
|
4146
|
+
return {
|
|
4147
|
+
api: this.swapApi(),
|
|
4148
|
+
readTargetBalance: () => dstPublic.readContract({
|
|
4149
|
+
address: quote.dst.address,
|
|
4150
|
+
abi: erc20Abi2,
|
|
4151
|
+
functionName: "balanceOf",
|
|
4152
|
+
args: [state.walletAddress]
|
|
4153
|
+
}),
|
|
4154
|
+
sendTransaction: async (tx) => {
|
|
4155
|
+
const hash = await wallet.sendTransaction({
|
|
4156
|
+
to: tx.to,
|
|
4157
|
+
data: tx.data,
|
|
4158
|
+
value: BigInt(tx.value || "0"),
|
|
4159
|
+
account: state.walletAddress,
|
|
4160
|
+
chain: srcChain
|
|
4161
|
+
});
|
|
4162
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
4163
|
+
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
4164
|
+
hash,
|
|
4165
|
+
confirmations: 1
|
|
4166
|
+
});
|
|
4167
|
+
if (receipt.status !== "success") {
|
|
4168
|
+
throw new OwneyError(
|
|
4169
|
+
"SWAP_REQUEST_FAILED",
|
|
4170
|
+
`Swap transaction reverted (tx ${hash})`,
|
|
4171
|
+
{ hash }
|
|
4172
|
+
);
|
|
4173
|
+
}
|
|
4174
|
+
return hash;
|
|
4175
|
+
},
|
|
4176
|
+
signTypedData: (typedData) => wallet.signTypedData({
|
|
4177
|
+
account: state.walletAddress,
|
|
4178
|
+
...typedData
|
|
4179
|
+
}),
|
|
4180
|
+
// Chain-bound like every other read here: the wallet provider's chain is
|
|
4181
|
+
// not ours to rely on mid-swap.
|
|
4182
|
+
readSourceBalance: async () => {
|
|
4183
|
+
const src = quote.src.address;
|
|
4184
|
+
if (src.toLowerCase().startsWith("0xeeee")) {
|
|
4185
|
+
return srcPublic.getBalance({ address: state.walletAddress });
|
|
4186
|
+
}
|
|
4187
|
+
return srcPublic.readContract({
|
|
4188
|
+
address: src,
|
|
4189
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4190
|
+
functionName: "balanceOf",
|
|
4191
|
+
args: [state.walletAddress]
|
|
4192
|
+
});
|
|
4193
|
+
},
|
|
4194
|
+
readAllowance: (spender) => srcPublic.readContract({
|
|
4195
|
+
address: quote.src.address,
|
|
4196
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4197
|
+
functionName: "allowance",
|
|
4198
|
+
args: [state.walletAddress, spender]
|
|
4199
|
+
}),
|
|
4200
|
+
approve: async (spender, amount) => {
|
|
4201
|
+
const hash = await wallet.writeContract({
|
|
4202
|
+
address: quote.src.address,
|
|
4203
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4204
|
+
functionName: "approve",
|
|
4205
|
+
args: [spender, amount],
|
|
4206
|
+
account: state.walletAddress,
|
|
4207
|
+
chain: srcChain
|
|
4208
|
+
});
|
|
4209
|
+
await srcPublic.waitForTransactionReceipt({
|
|
4210
|
+
hash,
|
|
4211
|
+
confirmations: 1,
|
|
4212
|
+
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
4213
|
+
});
|
|
4214
|
+
return hash;
|
|
4215
|
+
},
|
|
4216
|
+
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4217
|
+
runner: {
|
|
4218
|
+
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4219
|
+
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
4220
|
+
orderStatus: (h) => this.swapApi().orderStatus(h),
|
|
4221
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4222
|
+
now: () => Date.now()
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Assets the user may pay with, and what each chain deposits into.
|
|
4228
|
+
*
|
|
4229
|
+
* The source list is deliberately wider than the deposit list: it includes
|
|
4230
|
+
* native ETH and USDT, which Owney never holds but users often do.
|
|
4231
|
+
*/
|
|
4232
|
+
async getSwapTokens() {
|
|
4233
|
+
return this.swapApi().listTokens();
|
|
4234
|
+
}
|
|
4235
|
+
/**
|
|
4236
|
+
* Price a swap without committing to it.
|
|
4237
|
+
*
|
|
4238
|
+
* `dstAmountMin` is the number to validate against a deposit minimum —
|
|
4239
|
+
* `dst.amount` is an estimate that a decaying auction or slippage can undercut,
|
|
4240
|
+
* and a swap landing below the floor leaves the user swapped but not
|
|
4241
|
+
* deposited.
|
|
4242
|
+
*/
|
|
4243
|
+
async getSwapQuote(params) {
|
|
4244
|
+
const state = this.requireState();
|
|
4245
|
+
return this.swapApi().quote({ ...params, walletAddress: state.walletAddress });
|
|
4246
|
+
}
|
|
4247
|
+
/**
|
|
4248
|
+
* Swap an asset the user holds into a deposit asset, then deposit it.
|
|
4249
|
+
*
|
|
4250
|
+
* Kept separate from `deposit()` rather than bolted on as an option: the
|
|
4251
|
+
* return shape differs, the staging callback is meaningless on the plain
|
|
4252
|
+
* path, and integrators who never swap should not have to reason about any
|
|
4253
|
+
* of it.
|
|
4254
|
+
*
|
|
4255
|
+
* The deposit runs on the MEASURED arrival, not the quote. A quote is an
|
|
4256
|
+
* estimate, so depositing the quoted figure would either strand dust or try
|
|
4257
|
+
* to move funds that never came.
|
|
4258
|
+
*
|
|
4259
|
+
* Failure modes differ in a way callers must respect. A same-chain swap is
|
|
4260
|
+
* atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
|
|
4261
|
+
* funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
|
|
4262
|
+
* money left the wallet. Only the former can honestly say "nothing has left
|
|
4263
|
+
* your wallet".
|
|
4264
|
+
*/
|
|
4265
|
+
async swapAndDeposit(options) {
|
|
4266
|
+
const state = this.requireState();
|
|
4267
|
+
const api = this.swapApi();
|
|
4268
|
+
const quote = await api.quote({
|
|
4269
|
+
from: options.from,
|
|
4270
|
+
to: options.to,
|
|
4271
|
+
walletAddress: state.walletAddress
|
|
4272
|
+
});
|
|
4273
|
+
await this.ensureSwapChain(quote.src.chainId);
|
|
4274
|
+
const swap = await executeSwap(this.buildSwapDeps(quote), {
|
|
4275
|
+
quote,
|
|
4276
|
+
walletAddress: state.walletAddress,
|
|
4277
|
+
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4278
|
+
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4279
|
+
});
|
|
4280
|
+
options.onSwapProgress?.("depositing");
|
|
4281
|
+
await this.ensureSwapChain(quote.dst.chainId);
|
|
4282
|
+
const deposit = await this.deposit({
|
|
4283
|
+
amount: swap.received,
|
|
4284
|
+
asset: options.to.symbol,
|
|
4285
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
4286
|
+
});
|
|
4287
|
+
return { swap, deposit };
|
|
4288
|
+
}
|
|
3525
4289
|
/**
|
|
3526
4290
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
3527
4291
|
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
@@ -3557,19 +4321,9 @@ var OwneySDK = class {
|
|
|
3557
4321
|
}
|
|
3558
4322
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
3559
4323
|
if (!amount) {
|
|
3560
|
-
const aggregated2 = await this.getBalances();
|
|
3561
|
-
const funded = projectAgentBalancesForAsset(
|
|
3562
|
-
eligibleAgents,
|
|
3563
|
-
aggregated2.agentBalances,
|
|
3564
|
-
chainId,
|
|
3565
|
-
asset,
|
|
3566
|
-
assetInfo.decimals
|
|
3567
|
-
).filter(
|
|
3568
|
-
({ agent, balance }) => balance > 0n || aggregated2.agentErrors?.[agent.id] !== void 0
|
|
3569
|
-
).map(({ agent }) => agent);
|
|
3570
4324
|
const results2 = {};
|
|
3571
4325
|
const agentErrors2 = {};
|
|
3572
|
-
for (const agent of
|
|
4326
|
+
for (const agent of eligibleAgents) {
|
|
3573
4327
|
try {
|
|
3574
4328
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
3575
4329
|
} catch (err) {
|
|
@@ -4158,8 +4912,8 @@ import { SiweMessage } from "siwe";
|
|
|
4158
4912
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
4159
4913
|
|
|
4160
4914
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4161
|
-
var
|
|
4162
|
-
var
|
|
4915
|
+
var KEY_PREFIX3 = "owney.siwx.session";
|
|
4916
|
+
var storage3 = () => {
|
|
4163
4917
|
if (typeof window === "undefined") return null;
|
|
4164
4918
|
try {
|
|
4165
4919
|
return window.localStorage;
|
|
@@ -4167,8 +4921,8 @@ var storage2 = () => {
|
|
|
4167
4921
|
return null;
|
|
4168
4922
|
}
|
|
4169
4923
|
};
|
|
4170
|
-
var buildKey2 = (address) => `${
|
|
4171
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
4924
|
+
var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
|
|
4925
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
|
|
4172
4926
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4173
4927
|
var readLegacySiwxSession = (store, address) => {
|
|
4174
4928
|
if (!store) return null;
|
|
@@ -4200,7 +4954,7 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4200
4954
|
var readSiwxSession = (address, chainId) => {
|
|
4201
4955
|
if (typeof window === "undefined") return null;
|
|
4202
4956
|
const key2 = buildKey2(address);
|
|
4203
|
-
const store =
|
|
4957
|
+
const store = storage3();
|
|
4204
4958
|
let raw = null;
|
|
4205
4959
|
try {
|
|
4206
4960
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -4230,7 +4984,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4230
4984
|
if (typeof window === "undefined") return;
|
|
4231
4985
|
const key2 = buildKey2(address);
|
|
4232
4986
|
memorySiwxSessions.set(key2, session);
|
|
4233
|
-
const store =
|
|
4987
|
+
const store = storage3();
|
|
4234
4988
|
try {
|
|
4235
4989
|
store?.setItem(key2, JSON.stringify(session));
|
|
4236
4990
|
} catch {
|
|
@@ -4239,7 +4993,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4239
4993
|
var clearSiwxSession = (address, _chainId) => {
|
|
4240
4994
|
const key2 = buildKey2(address);
|
|
4241
4995
|
memorySiwxSessions.delete(key2);
|
|
4242
|
-
const store =
|
|
4996
|
+
const store = storage3();
|
|
4243
4997
|
try {
|
|
4244
4998
|
store?.removeItem(key2);
|
|
4245
4999
|
} catch {
|
|
@@ -4358,9 +5112,9 @@ function buildSIWXConfig(deps) {
|
|
|
4358
5112
|
}
|
|
4359
5113
|
function createOwneySIWX(config) {
|
|
4360
5114
|
const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
|
|
4361
|
-
const
|
|
5115
|
+
const http4 = zyfai.httpClient;
|
|
4362
5116
|
return buildSIWXConfig({
|
|
4363
|
-
post: (url, data) =>
|
|
5117
|
+
post: (url, data) => http4.post(url, data),
|
|
4364
5118
|
referralSource: config.referralSource
|
|
4365
5119
|
});
|
|
4366
5120
|
}
|
|
@@ -4372,5 +5126,6 @@ export {
|
|
|
4372
5126
|
OwneyError,
|
|
4373
5127
|
OwneySDK,
|
|
4374
5128
|
createOwneySIWX,
|
|
5129
|
+
listOrders as listPendingSwaps,
|
|
4375
5130
|
setOwneyDebug
|
|
4376
5131
|
};
|