@owney/sdk 0.7.23-beta.1 → 0.7.24-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 +290 -831
- package/dist/index.d.cts +77 -247
- package/dist/index.d.ts +77 -247
- package/dist/index.js +272 -813
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -664,6 +664,75 @@ function computeAllocationApy(positions) {
|
|
|
664
664
|
const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
|
|
665
665
|
return { totalApy, apyByChainAndAsset };
|
|
666
666
|
}
|
|
667
|
+
function earningsForToken(bucket, chainId, asset) {
|
|
668
|
+
const tokens = bucket?.[String(chainId)];
|
|
669
|
+
if (!tokens) return null;
|
|
670
|
+
const wanted = asset.toUpperCase();
|
|
671
|
+
for (const [symbol, value] of Object.entries(tokens)) {
|
|
672
|
+
if (symbol.toUpperCase() !== wanted) continue;
|
|
673
|
+
const amount = Number(value);
|
|
674
|
+
return Number.isFinite(amount) ? amount : null;
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
function assetsInSnapshot(entry, chainId) {
|
|
679
|
+
const key2 = String(chainId);
|
|
680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
681
|
+
for (const bucket of [
|
|
682
|
+
entry.daily_total_delta_by_token_withoutFee,
|
|
683
|
+
entry.daily_total_delta_by_token,
|
|
684
|
+
entry.lifetime_earnings_by_token,
|
|
685
|
+
entry.unrealized_earnings_by_token,
|
|
686
|
+
entry.current_earnings_by_token,
|
|
687
|
+
entry.total_earnings_by_token
|
|
688
|
+
]) {
|
|
689
|
+
for (const symbol of Object.keys(bucket?.[key2] ?? {})) {
|
|
690
|
+
seen.add(symbol.toUpperCase());
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return [...seen];
|
|
694
|
+
}
|
|
695
|
+
function netDeltaForSnapshot(entry, chainId, asset) {
|
|
696
|
+
const net = earningsForToken(
|
|
697
|
+
entry.daily_total_delta_by_token_withoutFee,
|
|
698
|
+
chainId,
|
|
699
|
+
asset
|
|
700
|
+
);
|
|
701
|
+
if (net !== null) return net;
|
|
702
|
+
const gross = earningsForToken(
|
|
703
|
+
entry.daily_total_delta_by_token,
|
|
704
|
+
chainId,
|
|
705
|
+
asset
|
|
706
|
+
);
|
|
707
|
+
if (gross === null) return 0;
|
|
708
|
+
if (!warnedGrossApyFallbacks.has("daily_earnings_delta_without_fee")) {
|
|
709
|
+
warnedGrossApyFallbacks.add("daily_earnings_delta_without_fee");
|
|
710
|
+
console.warn(
|
|
711
|
+
`[owney] @zyfai/sdk did not supply daily_total_delta_by_token_withoutFee; falling back to the gross daily delta, which does not deduct Zyfai's performance fee and so reads high.`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
|
|
715
|
+
return gross;
|
|
716
|
+
}
|
|
717
|
+
function mapDailyEarnings(raw, chainId, tokenSymbol) {
|
|
718
|
+
const wanted = tokenSymbol?.toUpperCase();
|
|
719
|
+
const snapshots = [...raw.data ?? []].sort(
|
|
720
|
+
(a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
|
|
721
|
+
);
|
|
722
|
+
const byAsset = /* @__PURE__ */ new Map();
|
|
723
|
+
for (const entry of snapshots) {
|
|
724
|
+
for (const asset of assetsInSnapshot(entry, chainId)) {
|
|
725
|
+
if (wanted && asset !== wanted) continue;
|
|
726
|
+
const delta = netDeltaForSnapshot(entry, chainId, asset);
|
|
727
|
+
const series = byAsset.get(asset) ?? { points: [], total: 0 };
|
|
728
|
+
series.total += delta;
|
|
729
|
+
series.points.push({ date: entry.snapshot_date, amount: series.total });
|
|
730
|
+
byAsset.set(asset, series);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
|
|
734
|
+
return { walletAddress: raw.walletAddress, chainId, assets };
|
|
735
|
+
}
|
|
667
736
|
|
|
668
737
|
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
669
738
|
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
@@ -1910,6 +1979,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1910
1979
|
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
1911
1980
|
return mapApyHistory(raw, chainId, tokenSymbol);
|
|
1912
1981
|
}
|
|
1982
|
+
async getDailyEarnings(state, chainId, days, tokenSymbol) {
|
|
1983
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1984
|
+
const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
|
|
1985
|
+
const raw = await this.sdk.getDailyEarnings(
|
|
1986
|
+
smartWallet,
|
|
1987
|
+
start.toISOString().slice(0, 10)
|
|
1988
|
+
);
|
|
1989
|
+
return mapDailyEarnings(raw, chainId, tokenSymbol);
|
|
1990
|
+
}
|
|
1913
1991
|
/**
|
|
1914
1992
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
1915
1993
|
* takes lowercase `assetType` and denominates WETH as "eth" (the same
|
|
@@ -2087,597 +2165,6 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
|
2087
2165
|
return json.data;
|
|
2088
2166
|
}
|
|
2089
2167
|
|
|
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 }
|
|
2546
|
-
);
|
|
2547
|
-
}
|
|
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
|
-
}
|
|
2680
|
-
|
|
2681
2168
|
// src/lib/health-report.ts
|
|
2682
2169
|
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2683
2170
|
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
@@ -2871,13 +2358,12 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
2871
2358
|
import {
|
|
2872
2359
|
createPublicClient as createPublicClient2,
|
|
2873
2360
|
createWalletClient,
|
|
2874
|
-
custom
|
|
2875
|
-
erc20Abi as erc20Abi2
|
|
2361
|
+
custom
|
|
2876
2362
|
} from "viem";
|
|
2877
2363
|
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2878
2364
|
|
|
2879
2365
|
// src/lib/transfer-auth.ts
|
|
2880
|
-
import { bytesToHex
|
|
2366
|
+
import { bytesToHex } from "viem";
|
|
2881
2367
|
var ERC20_META_ABI = [
|
|
2882
2368
|
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2883
2369
|
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
@@ -2909,7 +2395,7 @@ async function readTokenMeta(publicClient, token) {
|
|
|
2909
2395
|
function randomAuthNonce() {
|
|
2910
2396
|
const bytes = new Uint8Array(32);
|
|
2911
2397
|
globalThis.crypto.getRandomValues(bytes);
|
|
2912
|
-
return
|
|
2398
|
+
return bytesToHex(bytes);
|
|
2913
2399
|
}
|
|
2914
2400
|
|
|
2915
2401
|
// src/lib/sponsor-client.ts
|
|
@@ -3029,6 +2515,119 @@ async function getSponsorRelayerAddress(input) {
|
|
|
3029
2515
|
return parsed.data.relayer;
|
|
3030
2516
|
}
|
|
3031
2517
|
|
|
2518
|
+
// src/lib/permit2.ts
|
|
2519
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2520
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2521
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2522
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
2523
|
+
{
|
|
2524
|
+
type: "function",
|
|
2525
|
+
name: "allowance",
|
|
2526
|
+
stateMutability: "view",
|
|
2527
|
+
inputs: [
|
|
2528
|
+
{ name: "owner", type: "address" },
|
|
2529
|
+
{ name: "spender", type: "address" }
|
|
2530
|
+
],
|
|
2531
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
type: "function",
|
|
2535
|
+
name: "approve",
|
|
2536
|
+
stateMutability: "nonpayable",
|
|
2537
|
+
inputs: [
|
|
2538
|
+
{ name: "spender", type: "address" },
|
|
2539
|
+
{ name: "amount", type: "uint256" }
|
|
2540
|
+
],
|
|
2541
|
+
outputs: [{ name: "", type: "bool" }]
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
type: "function",
|
|
2545
|
+
name: "balanceOf",
|
|
2546
|
+
stateMutability: "view",
|
|
2547
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2548
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2549
|
+
}
|
|
2550
|
+
];
|
|
2551
|
+
function buildPermitTransferFromTypedData(input) {
|
|
2552
|
+
return {
|
|
2553
|
+
domain: {
|
|
2554
|
+
name: "Permit2",
|
|
2555
|
+
chainId: input.chainId,
|
|
2556
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
2557
|
+
},
|
|
2558
|
+
types: {
|
|
2559
|
+
PermitTransferFrom: [
|
|
2560
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
2561
|
+
{ name: "spender", type: "address" },
|
|
2562
|
+
{ name: "nonce", type: "uint256" },
|
|
2563
|
+
{ name: "deadline", type: "uint256" }
|
|
2564
|
+
],
|
|
2565
|
+
TokenPermissions: [
|
|
2566
|
+
{ name: "token", type: "address" },
|
|
2567
|
+
{ name: "amount", type: "uint256" }
|
|
2568
|
+
]
|
|
2569
|
+
},
|
|
2570
|
+
primaryType: "PermitTransferFrom",
|
|
2571
|
+
message: input.message
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
function randomPermit2Nonce() {
|
|
2575
|
+
const bytes = new Uint8Array(32);
|
|
2576
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2577
|
+
return BigInt(bytesToHex2(bytes));
|
|
2578
|
+
}
|
|
2579
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2580
|
+
return publicClient.readContract({
|
|
2581
|
+
address: token,
|
|
2582
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2583
|
+
functionName: "allowance",
|
|
2584
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2588
|
+
return publicClient.readContract({
|
|
2589
|
+
address: token,
|
|
2590
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2591
|
+
functionName: "balanceOf",
|
|
2592
|
+
args: [owner]
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
// src/lib/chain-guard.ts
|
|
2597
|
+
var CHAIN_NAMES = {
|
|
2598
|
+
1: "Ethereum",
|
|
2599
|
+
8453: "Base",
|
|
2600
|
+
42161: "Arbitrum"
|
|
2601
|
+
};
|
|
2602
|
+
function chainName(chainId) {
|
|
2603
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2604
|
+
}
|
|
2605
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2606
|
+
const actual = await pub.getChainId();
|
|
2607
|
+
if (actual === expected) return;
|
|
2608
|
+
try {
|
|
2609
|
+
await wallet.switchChain({ id: expected });
|
|
2610
|
+
} catch (error) {
|
|
2611
|
+
throw new OwneyError(
|
|
2612
|
+
"CHAIN_MISMATCH",
|
|
2613
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2614
|
+
{
|
|
2615
|
+
expectedChainId: expected,
|
|
2616
|
+
actualChainId: actual,
|
|
2617
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2618
|
+
}
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
const after = await pub.getChainId();
|
|
2622
|
+
if (after !== expected) {
|
|
2623
|
+
throw new OwneyError(
|
|
2624
|
+
"CHAIN_MISMATCH",
|
|
2625
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2626
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
3032
2631
|
// src/lib/sponsored-deposit.ts
|
|
3033
2632
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
3034
2633
|
function makeSponsoredDepositCallback(deps) {
|
|
@@ -3191,7 +2790,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
3191
2790
|
}
|
|
3192
2791
|
|
|
3193
2792
|
// src/lib/sponsored-calls-deposit.ts
|
|
3194
|
-
import { encodeFunctionData, erc20Abi, toHex
|
|
2793
|
+
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
3195
2794
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
3196
2795
|
var DEFAULT_MAX_POLLS = 30;
|
|
3197
2796
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -3199,7 +2798,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
3199
2798
|
method: "wallet_getCapabilities",
|
|
3200
2799
|
params: [owner]
|
|
3201
2800
|
});
|
|
3202
|
-
const forChain = caps?.[
|
|
2801
|
+
const forChain = caps?.[toHex(chainId)] ?? caps?.[String(chainId)];
|
|
3203
2802
|
return Boolean(forChain?.paymasterService?.supported);
|
|
3204
2803
|
}
|
|
3205
2804
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -3244,7 +2843,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3244
2843
|
{
|
|
3245
2844
|
version: "2.0.0",
|
|
3246
2845
|
from: deps.ownerAddress,
|
|
3247
|
-
chainId:
|
|
2846
|
+
chainId: toHex(chainId),
|
|
3248
2847
|
atomicRequired: false,
|
|
3249
2848
|
calls: [{ to: token, value: "0x0", data }],
|
|
3250
2849
|
capabilities: {
|
|
@@ -4081,211 +3680,6 @@ var OwneySDK = class {
|
|
|
4081
3680
|
return eligible;
|
|
4082
3681
|
}
|
|
4083
3682
|
// --- 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
|
-
}
|
|
4094
|
-
/**
|
|
4095
|
-
* Put the wallet on `chainId`, or fail with something actionable.
|
|
4096
|
-
*
|
|
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.
|
|
4100
|
-
*/
|
|
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 }
|
|
4110
|
-
);
|
|
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
|
|
4120
|
-
);
|
|
4121
|
-
}
|
|
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
|
-
}
|
|
4289
3683
|
/**
|
|
4290
3684
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
4291
3685
|
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
@@ -4566,6 +3960,72 @@ var OwneySDK = class {
|
|
|
4566
3960
|
* @param options.days - Lookback period: "7D", "14D", or "30D"
|
|
4567
3961
|
* @returns {AccountAgentApy} for a single agent, or {OwneyAccountApy} with totalApy and per-agent breakdown
|
|
4568
3962
|
*/
|
|
3963
|
+
/**
|
|
3964
|
+
* Daily cumulative NET earnings for the selected chain/asset, backing the
|
|
3965
|
+
* "recent earnings" subline. Net is computed as Zyfai's own
|
|
3966
|
+
* `lifetime + unrealized + current x 0.9`, so the figure reconciles with the
|
|
3967
|
+
* balance headline rather than reading ~11% high. (ROUT-452)
|
|
3968
|
+
*
|
|
3969
|
+
* Unlike getAccountApy this does NOT blend across agents: earnings are
|
|
3970
|
+
* summed, not weighted, and an agent that fails to report must not silently
|
|
3971
|
+
* subtract from the total. Without an agentId the series is the sum of the
|
|
3972
|
+
* agents that answered.
|
|
3973
|
+
*/
|
|
3974
|
+
async getDailyEarnings({
|
|
3975
|
+
agentId,
|
|
3976
|
+
days,
|
|
3977
|
+
tokenSymbol
|
|
3978
|
+
}) {
|
|
3979
|
+
const state = this.requireState();
|
|
3980
|
+
const chainId = this.requireChainId();
|
|
3981
|
+
if (agentId) {
|
|
3982
|
+
const agent = this.getAgent(agentId);
|
|
3983
|
+
if (!agent.getDailyEarnings) {
|
|
3984
|
+
return {
|
|
3985
|
+
walletAddress: state.walletAddress ?? "",
|
|
3986
|
+
chainId,
|
|
3987
|
+
assets: []
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
return this.readAgent(
|
|
3991
|
+
agent,
|
|
3992
|
+
"dailyEarnings",
|
|
3993
|
+
() => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
|
|
3994
|
+
{ days, tokenSymbol }
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
const entries = [...this.getActiveAgents().entries()].filter(
|
|
3998
|
+
([, agent]) => agent.getDailyEarnings
|
|
3999
|
+
);
|
|
4000
|
+
const series = await Promise.all(
|
|
4001
|
+
entries.map(
|
|
4002
|
+
([, agent]) => this.readAgent(
|
|
4003
|
+
agent,
|
|
4004
|
+
"dailyEarnings",
|
|
4005
|
+
() => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
|
|
4006
|
+
{ days, tokenSymbol }
|
|
4007
|
+
)
|
|
4008
|
+
)
|
|
4009
|
+
);
|
|
4010
|
+
const byAsset = /* @__PURE__ */ new Map();
|
|
4011
|
+
for (const s of series) {
|
|
4012
|
+
for (const { asset, points } of s.assets) {
|
|
4013
|
+
const byDate = byAsset.get(asset) ?? /* @__PURE__ */ new Map();
|
|
4014
|
+
for (const point of points) {
|
|
4015
|
+
byDate.set(point.date, (byDate.get(point.date) ?? 0) + point.amount);
|
|
4016
|
+
}
|
|
4017
|
+
byAsset.set(asset, byDate);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return {
|
|
4021
|
+
walletAddress: series[0]?.walletAddress ?? state.walletAddress ?? "",
|
|
4022
|
+
chainId,
|
|
4023
|
+
assets: [...byAsset.entries()].map(([asset, byDate]) => ({
|
|
4024
|
+
asset,
|
|
4025
|
+
points: [...byDate.entries()].map(([date, amount]) => ({ date, amount })).sort((a, b) => a.date.localeCompare(b.date))
|
|
4026
|
+
})).sort((a, b) => a.asset.localeCompare(b.asset))
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4569
4029
|
async getAccountApy({
|
|
4570
4030
|
agentId,
|
|
4571
4031
|
days,
|
|
@@ -4912,8 +4372,8 @@ import { SiweMessage } from "siwe";
|
|
|
4912
4372
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
4913
4373
|
|
|
4914
4374
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4915
|
-
var
|
|
4916
|
-
var
|
|
4375
|
+
var KEY_PREFIX2 = "owney.siwx.session";
|
|
4376
|
+
var storage2 = () => {
|
|
4917
4377
|
if (typeof window === "undefined") return null;
|
|
4918
4378
|
try {
|
|
4919
4379
|
return window.localStorage;
|
|
@@ -4921,8 +4381,8 @@ var storage3 = () => {
|
|
|
4921
4381
|
return null;
|
|
4922
4382
|
}
|
|
4923
4383
|
};
|
|
4924
|
-
var buildKey2 = (address) => `${
|
|
4925
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
4384
|
+
var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
|
|
4385
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
|
|
4926
4386
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4927
4387
|
var readLegacySiwxSession = (store, address) => {
|
|
4928
4388
|
if (!store) return null;
|
|
@@ -4954,7 +4414,7 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4954
4414
|
var readSiwxSession = (address, chainId) => {
|
|
4955
4415
|
if (typeof window === "undefined") return null;
|
|
4956
4416
|
const key2 = buildKey2(address);
|
|
4957
|
-
const store =
|
|
4417
|
+
const store = storage2();
|
|
4958
4418
|
let raw = null;
|
|
4959
4419
|
try {
|
|
4960
4420
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -4984,7 +4444,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4984
4444
|
if (typeof window === "undefined") return;
|
|
4985
4445
|
const key2 = buildKey2(address);
|
|
4986
4446
|
memorySiwxSessions.set(key2, session);
|
|
4987
|
-
const store =
|
|
4447
|
+
const store = storage2();
|
|
4988
4448
|
try {
|
|
4989
4449
|
store?.setItem(key2, JSON.stringify(session));
|
|
4990
4450
|
} catch {
|
|
@@ -4993,7 +4453,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4993
4453
|
var clearSiwxSession = (address, _chainId) => {
|
|
4994
4454
|
const key2 = buildKey2(address);
|
|
4995
4455
|
memorySiwxSessions.delete(key2);
|
|
4996
|
-
const store =
|
|
4456
|
+
const store = storage2();
|
|
4997
4457
|
try {
|
|
4998
4458
|
store?.removeItem(key2);
|
|
4999
4459
|
} catch {
|
|
@@ -5112,9 +4572,9 @@ function buildSIWXConfig(deps) {
|
|
|
5112
4572
|
}
|
|
5113
4573
|
function createOwneySIWX(config) {
|
|
5114
4574
|
const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
|
|
5115
|
-
const
|
|
4575
|
+
const http2 = zyfai.httpClient;
|
|
5116
4576
|
return buildSIWXConfig({
|
|
5117
|
-
post: (url, data) =>
|
|
4577
|
+
post: (url, data) => http2.post(url, data),
|
|
5118
4578
|
referralSource: config.referralSource
|
|
5119
4579
|
});
|
|
5120
4580
|
}
|
|
@@ -5126,6 +4586,5 @@ export {
|
|
|
5126
4586
|
OwneyError,
|
|
5127
4587
|
OwneySDK,
|
|
5128
4588
|
createOwneySIWX,
|
|
5129
|
-
listOrders as listPendingSwaps,
|
|
5130
4589
|
setOwneyDebug
|
|
5131
4590
|
};
|