@owney/sdk 0.7.21-beta.4 → 0.7.21-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/index.cjs +521 -393
- package/dist/index.d.cts +17 -22
- package/dist/index.d.ts +17 -22
- package/dist/index.js +509 -377
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
// src/lib/deposit-batch-callback.ts
|
|
2
|
+
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
4
|
+
function toBatchTransfer(to, amount, verification) {
|
|
5
|
+
return {
|
|
6
|
+
to,
|
|
7
|
+
amount,
|
|
8
|
+
...verification ? {
|
|
9
|
+
yieldseeker: {
|
|
10
|
+
signature: verification.signature,
|
|
11
|
+
userId: verification.userId,
|
|
12
|
+
agentId: verification.yieldseekerAgentId
|
|
13
|
+
}
|
|
14
|
+
} : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function registerDepositBatch(callback, transfer) {
|
|
18
|
+
batchCallbacks.set(callback, transfer);
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
// src/errors.ts
|
|
2
22
|
var OwneyError = class extends Error {
|
|
3
23
|
code;
|
|
@@ -2072,39 +2092,6 @@ async function ensureWalletOnChain(pub, wallet, expected) {
|
|
|
2072
2092
|
|
|
2073
2093
|
// src/lib/transfer-auth.ts
|
|
2074
2094
|
import { bytesToHex } from "viem";
|
|
2075
|
-
var ERC20_META_ABI = [
|
|
2076
|
-
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2077
|
-
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2078
|
-
];
|
|
2079
|
-
function buildTransferWithAuthorizationTypedData(input) {
|
|
2080
|
-
return {
|
|
2081
|
-
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2082
|
-
types: {
|
|
2083
|
-
TransferWithAuthorization: [
|
|
2084
|
-
{ name: "from", type: "address" },
|
|
2085
|
-
{ name: "to", type: "address" },
|
|
2086
|
-
{ name: "value", type: "uint256" },
|
|
2087
|
-
{ name: "validAfter", type: "uint256" },
|
|
2088
|
-
{ name: "validBefore", type: "uint256" },
|
|
2089
|
-
{ name: "nonce", type: "bytes32" }
|
|
2090
|
-
]
|
|
2091
|
-
},
|
|
2092
|
-
primaryType: "TransferWithAuthorization",
|
|
2093
|
-
message: input.message
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
async function readTokenMeta(publicClient, token) {
|
|
2097
|
-
const [tokenName, tokenVersion] = await Promise.all([
|
|
2098
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2099
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2100
|
-
]);
|
|
2101
|
-
return { tokenName, tokenVersion };
|
|
2102
|
-
}
|
|
2103
|
-
function randomAuthNonce() {
|
|
2104
|
-
const bytes = new Uint8Array(32);
|
|
2105
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2106
|
-
return bytesToHex(bytes);
|
|
2107
|
-
}
|
|
2108
2095
|
|
|
2109
2096
|
// src/lib/sponsor-client.ts
|
|
2110
2097
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
@@ -2147,65 +2134,21 @@ async function postPaymasterIntent(input) {
|
|
|
2147
2134
|
}
|
|
2148
2135
|
return parsed.data;
|
|
2149
2136
|
}
|
|
2150
|
-
async function
|
|
2137
|
+
async function getSponsorRelayerAddress(input) {
|
|
2151
2138
|
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2152
2139
|
let res;
|
|
2153
2140
|
try {
|
|
2154
|
-
res = await fetch(
|
|
2155
|
-
|
|
2156
|
-
headers: {
|
|
2157
|
-
"content-type": "application/json",
|
|
2158
|
-
"x-owney-api-key": input.apiKey,
|
|
2159
|
-
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2160
|
-
},
|
|
2161
|
-
body: JSON.stringify(input.body)
|
|
2162
|
-
});
|
|
2163
|
-
} catch (networkError) {
|
|
2164
|
-
throw new OwneyError(
|
|
2165
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2166
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2167
|
-
{ cause: String(networkError) }
|
|
2168
|
-
);
|
|
2169
|
-
}
|
|
2170
|
-
const text = await res.text();
|
|
2171
|
-
let parsed = null;
|
|
2172
|
-
try {
|
|
2173
|
-
parsed = JSON.parse(text);
|
|
2174
|
-
} catch {
|
|
2175
|
-
}
|
|
2176
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2177
|
-
throw new OwneyError(
|
|
2178
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2179
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2141
|
+
res = await fetch(
|
|
2142
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2180
2143
|
{
|
|
2181
|
-
|
|
2182
|
-
responseBody: text.slice(0, 500),
|
|
2183
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2184
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2185
|
-
safeToFallback: res.status === 503
|
|
2144
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
2186
2145
|
}
|
|
2187
2146
|
);
|
|
2188
|
-
}
|
|
2189
|
-
return parsed.data;
|
|
2190
|
-
}
|
|
2191
|
-
async function postSponsorPermit2Transfer(input) {
|
|
2192
|
-
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2193
|
-
let res;
|
|
2194
|
-
try {
|
|
2195
|
-
res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
|
|
2196
|
-
method: "POST",
|
|
2197
|
-
headers: {
|
|
2198
|
-
"content-type": "application/json",
|
|
2199
|
-
"x-owney-api-key": input.apiKey,
|
|
2200
|
-
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2201
|
-
},
|
|
2202
|
-
body: JSON.stringify(input.body)
|
|
2203
|
-
});
|
|
2204
2147
|
} catch (networkError) {
|
|
2205
2148
|
throw new OwneyError(
|
|
2206
2149
|
"SPONSOR_REQUEST_FAILED",
|
|
2207
2150
|
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2208
|
-
{ cause: String(networkError), safeToFallback:
|
|
2151
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2209
2152
|
);
|
|
2210
2153
|
}
|
|
2211
2154
|
const text = await res.text();
|
|
@@ -2214,60 +2157,64 @@ async function postSponsorPermit2Transfer(input) {
|
|
|
2214
2157
|
parsed = JSON.parse(text);
|
|
2215
2158
|
} catch {
|
|
2216
2159
|
}
|
|
2217
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2160
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2218
2161
|
throw new OwneyError(
|
|
2219
2162
|
"SPONSOR_REQUEST_FAILED",
|
|
2220
2163
|
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2221
2164
|
{
|
|
2222
2165
|
statusCode: res.status,
|
|
2223
2166
|
responseBody: text.slice(0, 500),
|
|
2224
|
-
safeToFallback:
|
|
2167
|
+
safeToFallback: true
|
|
2225
2168
|
}
|
|
2226
2169
|
);
|
|
2227
2170
|
}
|
|
2228
|
-
return parsed.data;
|
|
2171
|
+
return parsed.data.relayer;
|
|
2229
2172
|
}
|
|
2230
|
-
async function
|
|
2231
|
-
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2173
|
+
async function postSponsorBatchTransfer(input) {
|
|
2232
2174
|
let res;
|
|
2233
2175
|
try {
|
|
2234
2176
|
res = await fetch(
|
|
2235
|
-
`${
|
|
2177
|
+
`${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
|
|
2236
2178
|
{
|
|
2237
|
-
|
|
2179
|
+
method: "POST",
|
|
2180
|
+
headers: {
|
|
2181
|
+
"content-type": "application/json",
|
|
2182
|
+
"x-owney-api-key": input.apiKey
|
|
2183
|
+
},
|
|
2184
|
+
body: JSON.stringify(input.body)
|
|
2238
2185
|
}
|
|
2239
2186
|
);
|
|
2240
|
-
} catch
|
|
2187
|
+
} catch {
|
|
2241
2188
|
throw new OwneyError(
|
|
2242
2189
|
"SPONSOR_REQUEST_FAILED",
|
|
2243
|
-
|
|
2244
|
-
{
|
|
2190
|
+
"Deposit status is unknown. Retry the same amount to check it.",
|
|
2191
|
+
{ safeToFallback: false }
|
|
2245
2192
|
);
|
|
2246
2193
|
}
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
try {
|
|
2250
|
-
parsed = JSON.parse(text);
|
|
2251
|
-
} catch {
|
|
2252
|
-
}
|
|
2253
|
-
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2194
|
+
const parsed = await res.json().catch(() => null);
|
|
2195
|
+
if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
|
|
2254
2196
|
throw new OwneyError(
|
|
2255
2197
|
"SPONSOR_REQUEST_FAILED",
|
|
2256
|
-
|
|
2198
|
+
"Deposit could not be confirmed. Retry the same amount to check its status.",
|
|
2257
2199
|
{
|
|
2258
2200
|
statusCode: res.status,
|
|
2259
|
-
|
|
2260
|
-
|
|
2201
|
+
safeToFallback: false,
|
|
2202
|
+
notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
|
|
2261
2203
|
}
|
|
2262
2204
|
);
|
|
2263
2205
|
}
|
|
2264
|
-
return parsed.data
|
|
2206
|
+
return parsed.data;
|
|
2265
2207
|
}
|
|
2266
2208
|
|
|
2267
2209
|
// src/lib/permit2.ts
|
|
2268
2210
|
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2269
2211
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2270
|
-
|
|
2212
|
+
function permit2ApprovalAmount(requiredAmount) {
|
|
2213
|
+
if (requiredAmount <= 0n) {
|
|
2214
|
+
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2215
|
+
}
|
|
2216
|
+
return requiredAmount;
|
|
2217
|
+
}
|
|
2271
2218
|
var ERC20_ALLOWANCE_ABI = [
|
|
2272
2219
|
{
|
|
2273
2220
|
type: "function",
|
|
@@ -2297,29 +2244,6 @@ var ERC20_ALLOWANCE_ABI = [
|
|
|
2297
2244
|
outputs: [{ name: "", type: "uint256" }]
|
|
2298
2245
|
}
|
|
2299
2246
|
];
|
|
2300
|
-
function buildPermitTransferFromTypedData(input) {
|
|
2301
|
-
return {
|
|
2302
|
-
domain: {
|
|
2303
|
-
name: "Permit2",
|
|
2304
|
-
chainId: input.chainId,
|
|
2305
|
-
verifyingContract: PERMIT2_ADDRESS
|
|
2306
|
-
},
|
|
2307
|
-
types: {
|
|
2308
|
-
PermitTransferFrom: [
|
|
2309
|
-
{ name: "permitted", type: "TokenPermissions" },
|
|
2310
|
-
{ name: "spender", type: "address" },
|
|
2311
|
-
{ name: "nonce", type: "uint256" },
|
|
2312
|
-
{ name: "deadline", type: "uint256" }
|
|
2313
|
-
],
|
|
2314
|
-
TokenPermissions: [
|
|
2315
|
-
{ name: "token", type: "address" },
|
|
2316
|
-
{ name: "amount", type: "uint256" }
|
|
2317
|
-
]
|
|
2318
|
-
},
|
|
2319
|
-
primaryType: "PermitTransferFrom",
|
|
2320
|
-
message: input.message
|
|
2321
|
-
};
|
|
2322
|
-
}
|
|
2323
2247
|
function randomPermit2Nonce() {
|
|
2324
2248
|
const bytes = new Uint8Array(32);
|
|
2325
2249
|
globalThis.crypto.getRandomValues(bytes);
|
|
@@ -2362,88 +2286,6 @@ function makeVerificationAwareDepositCallback(implementation) {
|
|
|
2362
2286
|
});
|
|
2363
2287
|
return callback;
|
|
2364
2288
|
}
|
|
2365
|
-
function makeSponsoredDepositCallback(deps) {
|
|
2366
|
-
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2367
|
-
return makeVerificationAwareDepositCallback(
|
|
2368
|
-
async (smartWallet, chainId, amount, verification) => {
|
|
2369
|
-
const cid = chainId;
|
|
2370
|
-
const token = deps.tokenAddressByChain[cid];
|
|
2371
|
-
if (!token) {
|
|
2372
|
-
throw new OwneyError(
|
|
2373
|
-
"CHAIN_UNSUPPORTED",
|
|
2374
|
-
`No sponsored token configured for chain ${chainId}`
|
|
2375
|
-
);
|
|
2376
|
-
}
|
|
2377
|
-
const pub = deps.getPublicClient(cid);
|
|
2378
|
-
const wallet = deps.getWalletClient(cid);
|
|
2379
|
-
await ensureWalletOnChain(pub, wallet, cid);
|
|
2380
|
-
try {
|
|
2381
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2382
|
-
if (balance < BigInt(amount)) {
|
|
2383
|
-
throw new OwneyError(
|
|
2384
|
-
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2385
|
-
"Insufficient balance for this deposit.",
|
|
2386
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
} catch (err) {
|
|
2390
|
-
if (err instanceof OwneyError) throw err;
|
|
2391
|
-
console.warn(
|
|
2392
|
-
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2393
|
-
err instanceof Error ? err.message : String(err)
|
|
2394
|
-
);
|
|
2395
|
-
}
|
|
2396
|
-
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2397
|
-
const validAfter = 0n;
|
|
2398
|
-
const validBefore = BigInt(
|
|
2399
|
-
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2400
|
-
);
|
|
2401
|
-
const nonce = randomAuthNonce();
|
|
2402
|
-
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2403
|
-
token,
|
|
2404
|
-
chainId: cid,
|
|
2405
|
-
tokenName,
|
|
2406
|
-
tokenVersion,
|
|
2407
|
-
message: {
|
|
2408
|
-
from: deps.ownerAddress,
|
|
2409
|
-
to: smartWallet,
|
|
2410
|
-
value: BigInt(amount),
|
|
2411
|
-
validAfter,
|
|
2412
|
-
validBefore,
|
|
2413
|
-
nonce
|
|
2414
|
-
}
|
|
2415
|
-
});
|
|
2416
|
-
const authSignature = await wallet.signTypedData({
|
|
2417
|
-
account: deps.ownerAddress,
|
|
2418
|
-
...typedData
|
|
2419
|
-
});
|
|
2420
|
-
deps.onApproved?.();
|
|
2421
|
-
const result = await post({
|
|
2422
|
-
baseUrl: deps.baseUrl,
|
|
2423
|
-
apiKey: deps.apiKey,
|
|
2424
|
-
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
2425
|
-
body: {
|
|
2426
|
-
chainId: cid,
|
|
2427
|
-
token,
|
|
2428
|
-
from: deps.ownerAddress,
|
|
2429
|
-
to: smartWallet,
|
|
2430
|
-
value: amount,
|
|
2431
|
-
validAfter: validAfter.toString(),
|
|
2432
|
-
validBefore: validBefore.toString(),
|
|
2433
|
-
nonce,
|
|
2434
|
-
authSignature,
|
|
2435
|
-
tokenName,
|
|
2436
|
-
tokenVersion,
|
|
2437
|
-
...verification?.agentId === "yieldseeker" ? {
|
|
2438
|
-
yieldseekerUserId: verification.userId,
|
|
2439
|
-
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
2440
|
-
} : {}
|
|
2441
|
-
}
|
|
2442
|
-
});
|
|
2443
|
-
return result.txHash;
|
|
2444
|
-
}
|
|
2445
|
-
);
|
|
2446
|
-
}
|
|
2447
2289
|
|
|
2448
2290
|
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2449
2291
|
import { SiweMessage, generateNonce } from "siwe";
|
|
@@ -3127,7 +2969,7 @@ function depositDestination(context, movement) {
|
|
|
3127
2969
|
if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
|
|
3128
2970
|
return void 0;
|
|
3129
2971
|
}
|
|
3130
|
-
function movementEntry(movement, wallet, agent, asset, ownerAddress, destination) {
|
|
2972
|
+
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
|
|
3131
2973
|
const from = movement.fromAddress.toLowerCase();
|
|
3132
2974
|
const to = movement.toAddress.toLowerCase();
|
|
3133
2975
|
const owner = ownerAddress.toLowerCase();
|
|
@@ -3137,7 +2979,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, destination
|
|
|
3137
2979
|
return void 0;
|
|
3138
2980
|
}
|
|
3139
2981
|
let action;
|
|
3140
|
-
if (
|
|
2982
|
+
if (to === agentWallet && !vaultAddresses.has(from)) {
|
|
3141
2983
|
action = "Top up";
|
|
3142
2984
|
} else if (from === agentWallet && to === owner) {
|
|
3143
2985
|
action = "Withdraw";
|
|
@@ -3187,6 +3029,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3187
3029
|
context.agent,
|
|
3188
3030
|
context.asset,
|
|
3189
3031
|
options.ownerAddress,
|
|
3032
|
+
options.vaultAddresses,
|
|
3190
3033
|
depositDestination(context, movement)
|
|
3191
3034
|
)
|
|
3192
3035
|
),
|
|
@@ -3267,6 +3110,7 @@ var OWNEY_AGENT_NAME = "owney";
|
|
|
3267
3110
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3268
3111
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3269
3112
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3113
|
+
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3270
3114
|
function generateYieldseekerUsername() {
|
|
3271
3115
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3272
3116
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -3328,6 +3172,8 @@ var YieldseekerAgent = class {
|
|
|
3328
3172
|
agentContexts = /* @__PURE__ */ new Map();
|
|
3329
3173
|
users = /* @__PURE__ */ new Map();
|
|
3330
3174
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
3175
|
+
yieldOptions = /* @__PURE__ */ new Map();
|
|
3176
|
+
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3331
3177
|
constructor(owneyApiKey, options = {}) {
|
|
3332
3178
|
this.api = new YieldseekerApiClient(
|
|
3333
3179
|
owneyApiKey,
|
|
@@ -3394,7 +3240,6 @@ var YieldseekerAgent = class {
|
|
|
3394
3240
|
chainId
|
|
3395
3241
|
});
|
|
3396
3242
|
}
|
|
3397
|
-
await this.deployAfterFunding(state, chainId, context);
|
|
3398
3243
|
} finally {
|
|
3399
3244
|
await this.refreshSnapshotAfterMovement(
|
|
3400
3245
|
state,
|
|
@@ -3546,9 +3391,20 @@ var YieldseekerAgent = class {
|
|
|
3546
3391
|
historic: true,
|
|
3547
3392
|
actions: true
|
|
3548
3393
|
});
|
|
3394
|
+
const catalog = await Promise.all(
|
|
3395
|
+
[...new Set(contexts.map((context) => context.asset))].map(
|
|
3396
|
+
(contextAsset) => this.loadYieldOptions(contextAsset)
|
|
3397
|
+
)
|
|
3398
|
+
);
|
|
3399
|
+
const vaultAddresses = new Set(
|
|
3400
|
+
catalog.flat().filter(
|
|
3401
|
+
(yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
|
|
3402
|
+
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
3403
|
+
);
|
|
3549
3404
|
return mapYieldseekerHistory(contexts, {
|
|
3550
3405
|
limit: options?.limit ?? 10,
|
|
3551
3406
|
ownerAddress: state.walletAddress,
|
|
3407
|
+
vaultAddresses,
|
|
3552
3408
|
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3553
3409
|
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3554
3410
|
});
|
|
@@ -3567,18 +3423,32 @@ var YieldseekerAgent = class {
|
|
|
3567
3423
|
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3568
3424
|
const values = await Promise.all(
|
|
3569
3425
|
assets.map(async (asset) => {
|
|
3570
|
-
|
|
3571
|
-
const response = await this.api.request(
|
|
3572
|
-
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3573
|
-
);
|
|
3574
|
-
if (!Array.isArray(response?.yieldOptions)) {
|
|
3575
|
-
throw this.invalidResponse("yield options");
|
|
3576
|
-
}
|
|
3577
|
-
return { asset, yieldOptions: response.yieldOptions };
|
|
3426
|
+
return { asset, yieldOptions: await this.loadYieldOptions(asset) };
|
|
3578
3427
|
})
|
|
3579
3428
|
);
|
|
3580
3429
|
return mapYieldseekerAgentApy(values, days);
|
|
3581
3430
|
}
|
|
3431
|
+
async loadYieldOptions(asset) {
|
|
3432
|
+
const cached = this.yieldOptions.get(asset);
|
|
3433
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
3434
|
+
const pending = this.pendingYieldOptions.get(asset);
|
|
3435
|
+
if (pending) return pending;
|
|
3436
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3437
|
+
const request = this.api.request(
|
|
3438
|
+
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3439
|
+
).then((response) => {
|
|
3440
|
+
if (!Array.isArray(response?.yieldOptions)) {
|
|
3441
|
+
throw this.invalidResponse("yield options");
|
|
3442
|
+
}
|
|
3443
|
+
this.yieldOptions.set(asset, {
|
|
3444
|
+
expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
|
|
3445
|
+
value: response.yieldOptions
|
|
3446
|
+
});
|
|
3447
|
+
return response.yieldOptions;
|
|
3448
|
+
}).finally(() => this.pendingYieldOptions.delete(asset));
|
|
3449
|
+
this.pendingYieldOptions.set(asset, request);
|
|
3450
|
+
return request;
|
|
3451
|
+
}
|
|
3582
3452
|
userKey(state, chainId) {
|
|
3583
3453
|
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3584
3454
|
}
|
|
@@ -3657,8 +3527,9 @@ var YieldseekerAgent = class {
|
|
|
3657
3527
|
const pending = this.pendingAgents.get(key2);
|
|
3658
3528
|
if (pending) return pending;
|
|
3659
3529
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3660
|
-
(context) => {
|
|
3530
|
+
async (context) => {
|
|
3661
3531
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3532
|
+
await this.deployAgent(state, chainId, context);
|
|
3662
3533
|
this.agentContexts.set(key2, context);
|
|
3663
3534
|
return context;
|
|
3664
3535
|
}
|
|
@@ -3803,24 +3674,21 @@ var YieldseekerAgent = class {
|
|
|
3803
3674
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
3804
3675
|
};
|
|
3805
3676
|
}
|
|
3806
|
-
async
|
|
3677
|
+
async deployAgent(state, chainId, context) {
|
|
3807
3678
|
if (context.wallet.initializedDate != null) return;
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
console.warn(
|
|
3820
|
-
"[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
|
|
3821
|
-
error
|
|
3822
|
-
);
|
|
3679
|
+
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
3680
|
+
const deployed = await this.walletRequest(
|
|
3681
|
+
state,
|
|
3682
|
+
chainId,
|
|
3683
|
+
this.agentPath(context, "deploy"),
|
|
3684
|
+
{ method: "POST", body: {} }
|
|
3685
|
+
);
|
|
3686
|
+
if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
3687
|
+
throw this.invalidResponse("agent deployment", {
|
|
3688
|
+
reason: "Deploy did not return the expected Agent Wallet."
|
|
3689
|
+
});
|
|
3823
3690
|
}
|
|
3691
|
+
context.wallet = deployed.agentWallet;
|
|
3824
3692
|
}
|
|
3825
3693
|
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
3826
3694
|
try {
|
|
@@ -4311,98 +4179,302 @@ import {
|
|
|
4311
4179
|
} from "viem";
|
|
4312
4180
|
import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
4313
4181
|
|
|
4314
|
-
// src/lib/sponsored-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4182
|
+
// src/lib/sponsored-token-batch.ts
|
|
4183
|
+
import {
|
|
4184
|
+
isAddressEqual,
|
|
4185
|
+
keccak256,
|
|
4186
|
+
toBytes
|
|
4187
|
+
} from "viem";
|
|
4188
|
+
|
|
4189
|
+
// src/lib/permit2-batch.ts
|
|
4190
|
+
import { parseAbi as parseAbi2, hashStruct } from "viem";
|
|
4191
|
+
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
4192
|
+
var PERMIT_BATCH_TYPES = {
|
|
4193
|
+
PermitBatchWitnessTransferFrom: [
|
|
4194
|
+
{ name: "permitted", type: "TokenPermissions[]" },
|
|
4195
|
+
{ name: "spender", type: "address" },
|
|
4196
|
+
{ name: "nonce", type: "uint256" },
|
|
4197
|
+
{ name: "deadline", type: "uint256" },
|
|
4198
|
+
{ name: "witness", type: "Deposit" }
|
|
4199
|
+
],
|
|
4200
|
+
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
4201
|
+
TokenPermissions: [
|
|
4202
|
+
{ name: "token", type: "address" },
|
|
4203
|
+
{ name: "amount", type: "uint256" }
|
|
4204
|
+
]
|
|
4205
|
+
};
|
|
4206
|
+
var PERMIT2_BATCH_ABI = parseAbi2([
|
|
4207
|
+
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4208
|
+
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4209
|
+
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
4210
|
+
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
4211
|
+
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
4212
|
+
]);
|
|
4213
|
+
function batchPermit(b) {
|
|
4214
|
+
return {
|
|
4215
|
+
permitted: b.transfers.map((t) => ({
|
|
4216
|
+
token: b.token,
|
|
4217
|
+
amount: BigInt(t.amount)
|
|
4218
|
+
})),
|
|
4219
|
+
nonce: BigInt(b.nonce),
|
|
4220
|
+
deadline: BigInt(b.deadline)
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
function batchTypedData(b, spender) {
|
|
4224
|
+
return {
|
|
4225
|
+
domain: {
|
|
4226
|
+
name: "Permit2",
|
|
4227
|
+
chainId: b.chainId,
|
|
4228
|
+
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
4229
|
+
},
|
|
4230
|
+
types: PERMIT_BATCH_TYPES,
|
|
4231
|
+
primaryType: "PermitBatchWitnessTransferFrom",
|
|
4232
|
+
message: {
|
|
4233
|
+
...batchPermit(b),
|
|
4234
|
+
spender,
|
|
4235
|
+
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
4236
|
+
}
|
|
4237
|
+
};
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
// src/lib/sponsored-token-batch.ts
|
|
4241
|
+
var memory = /* @__PURE__ */ new Map();
|
|
4242
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
4243
|
+
var planOf = (transfers) => JSON.stringify(
|
|
4244
|
+
transfers.map((t) => ({
|
|
4245
|
+
to: t.to.toLowerCase(),
|
|
4246
|
+
amount: BigInt(t.amount).toString()
|
|
4247
|
+
}))
|
|
4248
|
+
);
|
|
4249
|
+
function read(key2) {
|
|
4250
|
+
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
4251
|
+
}
|
|
4252
|
+
function save(key2, body) {
|
|
4253
|
+
const value = JSON.stringify({
|
|
4254
|
+
...body,
|
|
4255
|
+
transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
|
|
4256
|
+
});
|
|
4257
|
+
if (typeof window === "undefined") memory.set(key2, value);
|
|
4258
|
+
else window.localStorage.setItem(key2, value);
|
|
4259
|
+
}
|
|
4260
|
+
function clear(key2) {
|
|
4261
|
+
if (typeof window === "undefined") memory.delete(key2);
|
|
4262
|
+
else window.localStorage.removeItem(key2);
|
|
4263
|
+
}
|
|
4264
|
+
function sponsorTokenBatch(i) {
|
|
4265
|
+
const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
4266
|
+
const plan = planOf(i.transfers);
|
|
4267
|
+
const active = inflight.get(key2);
|
|
4268
|
+
if (active) {
|
|
4269
|
+
if (active.plan !== plan)
|
|
4270
|
+
return Promise.reject(
|
|
4271
|
+
new Error(
|
|
4272
|
+
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
4273
|
+
)
|
|
4353
4274
|
);
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4275
|
+
return active.promise;
|
|
4276
|
+
}
|
|
4277
|
+
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
4278
|
+
inflight.set(key2, { plan, promise });
|
|
4279
|
+
return promise;
|
|
4280
|
+
}
|
|
4281
|
+
async function execute(i, key2, plan) {
|
|
4282
|
+
if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
|
|
4283
|
+
(t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
|
|
4284
|
+
) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
|
|
4285
|
+
throw new Error("Invalid token deposit shares.");
|
|
4286
|
+
const send = async (initial) => {
|
|
4287
|
+
let body = initial;
|
|
4288
|
+
save(key2, body);
|
|
4289
|
+
try {
|
|
4290
|
+
if (!body.serializedTransaction) {
|
|
4291
|
+
const prepared = await postSponsorBatchTransfer({
|
|
4292
|
+
apiKey: i.apiKey,
|
|
4293
|
+
baseUrl: i.baseUrl,
|
|
4294
|
+
body
|
|
4295
|
+
});
|
|
4296
|
+
if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
|
|
4297
|
+
throw new Error(
|
|
4298
|
+
"Sponsorship API did not return a valid prepared transaction."
|
|
4299
|
+
);
|
|
4300
|
+
body = {
|
|
4301
|
+
...body,
|
|
4302
|
+
serializedTransaction: prepared.serializedTransaction
|
|
4303
|
+
};
|
|
4304
|
+
save(key2, body);
|
|
4360
4305
|
}
|
|
4361
|
-
const
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
});
|
|
4366
|
-
const nonce = randomPermit2Nonce();
|
|
4367
|
-
const deadline = BigInt(
|
|
4368
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
4369
|
-
);
|
|
4370
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
4371
|
-
chainId: cid,
|
|
4372
|
-
message: {
|
|
4373
|
-
permitted: { token, amount: amountWei },
|
|
4374
|
-
spender: relayer,
|
|
4375
|
-
nonce,
|
|
4376
|
-
deadline
|
|
4377
|
-
}
|
|
4378
|
-
});
|
|
4379
|
-
const signature = await wallet.signTypedData({
|
|
4380
|
-
account: deps.ownerAddress,
|
|
4381
|
-
...typedData
|
|
4382
|
-
});
|
|
4383
|
-
deps.onApproved?.();
|
|
4384
|
-
const result = await post({
|
|
4385
|
-
baseUrl: deps.baseUrl,
|
|
4386
|
-
apiKey: deps.apiKey,
|
|
4387
|
-
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
4388
|
-
body: {
|
|
4389
|
-
chainId: cid,
|
|
4390
|
-
token,
|
|
4391
|
-
from: deps.ownerAddress,
|
|
4392
|
-
to: smartWallet,
|
|
4393
|
-
amount,
|
|
4394
|
-
nonce: nonce.toString(),
|
|
4395
|
-
deadline: deadline.toString(),
|
|
4396
|
-
signature,
|
|
4397
|
-
...verification?.agentId === "yieldseeker" ? {
|
|
4398
|
-
yieldseekerUserId: verification.userId,
|
|
4399
|
-
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
4400
|
-
} : {}
|
|
4401
|
-
}
|
|
4306
|
+
const result = await postSponsorBatchTransfer({
|
|
4307
|
+
apiKey: i.apiKey,
|
|
4308
|
+
baseUrl: i.baseUrl,
|
|
4309
|
+
body
|
|
4402
4310
|
});
|
|
4311
|
+
if (result.txHash !== keccak256(body.serializedTransaction))
|
|
4312
|
+
throw new Error(
|
|
4313
|
+
"Sponsorship receipt does not match the pending transaction."
|
|
4314
|
+
);
|
|
4315
|
+
clear(key2);
|
|
4403
4316
|
return result.txHash;
|
|
4317
|
+
} catch (error) {
|
|
4318
|
+
if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
4319
|
+
clear(key2);
|
|
4320
|
+
throw error;
|
|
4404
4321
|
}
|
|
4322
|
+
};
|
|
4323
|
+
const saved = read(key2);
|
|
4324
|
+
if (saved) {
|
|
4325
|
+
const previous = JSON.parse(saved);
|
|
4326
|
+
if (previous.chainId !== i.chainId || !isAddressEqual(previous.from, i.owner) || !isAddressEqual(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
4327
|
+
throw new Error(
|
|
4328
|
+
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
4329
|
+
);
|
|
4330
|
+
i.onApproved?.();
|
|
4331
|
+
return send({ ...previous, transfers: i.transfers });
|
|
4332
|
+
}
|
|
4333
|
+
const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
|
|
4334
|
+
const [balance, allowance] = await Promise.all([
|
|
4335
|
+
readErc20Balance(i.pub, i.token, i.owner),
|
|
4336
|
+
readPermit2Allowance(i.pub, i.token, i.owner)
|
|
4337
|
+
]);
|
|
4338
|
+
if (balance < total)
|
|
4339
|
+
throw new OwneyError(
|
|
4340
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4341
|
+
"Insufficient token balance for this deposit."
|
|
4342
|
+
);
|
|
4343
|
+
if (allowance < total)
|
|
4344
|
+
throw new OwneyError(
|
|
4345
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
4346
|
+
"token deposits need a one-time Permit2 approval."
|
|
4347
|
+
);
|
|
4348
|
+
const relayer = await getSponsorRelayerAddress({
|
|
4349
|
+
apiKey: i.apiKey,
|
|
4350
|
+
baseUrl: i.baseUrl,
|
|
4351
|
+
chainId: i.chainId
|
|
4352
|
+
});
|
|
4353
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
4354
|
+
const unsigned = {
|
|
4355
|
+
chainId: i.chainId,
|
|
4356
|
+
token: i.token,
|
|
4357
|
+
from: i.owner,
|
|
4358
|
+
transfers: i.transfers,
|
|
4359
|
+
nonce: randomPermit2Nonce().toString(),
|
|
4360
|
+
deadline: (now + 900n).toString()
|
|
4361
|
+
};
|
|
4362
|
+
const signature = await i.wallet.signTypedData({
|
|
4363
|
+
account: i.owner,
|
|
4364
|
+
...batchTypedData(unsigned, relayer)
|
|
4365
|
+
});
|
|
4366
|
+
i.onApproved?.();
|
|
4367
|
+
return send({ ...unsigned, signature });
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
// src/lib/sponsored-token-deposit.ts
|
|
4371
|
+
function makeSponsoredTokenCallback(deps) {
|
|
4372
|
+
const batch = async (chainId, transfers) => {
|
|
4373
|
+
if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
|
|
4374
|
+
throw new OwneyError(
|
|
4375
|
+
"CHAIN_UNSUPPORTED",
|
|
4376
|
+
`No sponsored token configured for chain ${chainId}`
|
|
4377
|
+
);
|
|
4378
|
+
const token = deps.tokenAddressByChain[chainId];
|
|
4379
|
+
if (!token)
|
|
4380
|
+
throw new OwneyError(
|
|
4381
|
+
"CHAIN_UNSUPPORTED",
|
|
4382
|
+
`No sponsored token configured for chain ${chainId}`
|
|
4383
|
+
);
|
|
4384
|
+
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4385
|
+
await ensureWalletOnChain(pub, wallet, chainId);
|
|
4386
|
+
return sponsorTokenBatch({
|
|
4387
|
+
apiKey: deps.apiKey,
|
|
4388
|
+
baseUrl: deps.baseUrl,
|
|
4389
|
+
owner: deps.ownerAddress,
|
|
4390
|
+
token,
|
|
4391
|
+
chainId,
|
|
4392
|
+
transfers,
|
|
4393
|
+
pub,
|
|
4394
|
+
wallet,
|
|
4395
|
+
onApproved: deps.onApproved
|
|
4396
|
+
});
|
|
4397
|
+
};
|
|
4398
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4399
|
+
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
4405
4400
|
);
|
|
4401
|
+
registerDepositBatch(callback, batch);
|
|
4402
|
+
return callback;
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
// src/lib/agent-deposit-batch.ts
|
|
4406
|
+
function deferred() {
|
|
4407
|
+
let resolve, reject;
|
|
4408
|
+
const promise = new Promise((yes, no) => {
|
|
4409
|
+
resolve = yes;
|
|
4410
|
+
reject = no;
|
|
4411
|
+
});
|
|
4412
|
+
void promise.catch(() => {
|
|
4413
|
+
});
|
|
4414
|
+
return { promise, resolve, reject };
|
|
4415
|
+
}
|
|
4416
|
+
async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
4417
|
+
const funding = deferred();
|
|
4418
|
+
const tasks = [];
|
|
4419
|
+
const transfers = [];
|
|
4420
|
+
try {
|
|
4421
|
+
for (const leg of legs) {
|
|
4422
|
+
const ready = deferred();
|
|
4423
|
+
let entered = false;
|
|
4424
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4425
|
+
(to, cid, amount, verification) => {
|
|
4426
|
+
if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
|
|
4427
|
+
const error = new Error(
|
|
4428
|
+
"Agent changed its prepared deposit share."
|
|
4429
|
+
);
|
|
4430
|
+
ready.reject(error);
|
|
4431
|
+
throw error;
|
|
4432
|
+
}
|
|
4433
|
+
entered = true;
|
|
4434
|
+
ready.resolve(toBatchTransfer(to, amount, verification));
|
|
4435
|
+
return funding.promise;
|
|
4436
|
+
}
|
|
4437
|
+
);
|
|
4438
|
+
const task = Promise.resolve().then(() => leg.run(callback));
|
|
4439
|
+
tasks.push(task);
|
|
4440
|
+
void task.then(
|
|
4441
|
+
() => {
|
|
4442
|
+
if (!entered)
|
|
4443
|
+
ready.reject(
|
|
4444
|
+
new Error("Agent did not prepare a deposit transfer.")
|
|
4445
|
+
);
|
|
4446
|
+
},
|
|
4447
|
+
(error) => ready.reject(error)
|
|
4448
|
+
);
|
|
4449
|
+
transfers.push(await ready.promise);
|
|
4450
|
+
}
|
|
4451
|
+
const txHash = await transfer(chainId, transfers);
|
|
4452
|
+
funding.resolve(txHash);
|
|
4453
|
+
const settled = await Promise.allSettled(tasks);
|
|
4454
|
+
const agentResults = {};
|
|
4455
|
+
const failures = [];
|
|
4456
|
+
for (const [index, result] of settled.entries()) {
|
|
4457
|
+
if (result.status === "fulfilled")
|
|
4458
|
+
agentResults[legs[index].id] = result.value;
|
|
4459
|
+
else failures.push(legs[index].id);
|
|
4460
|
+
}
|
|
4461
|
+
if (failures.length)
|
|
4462
|
+
throw new OwneyError(
|
|
4463
|
+
"DEPOSIT_PARTIAL_FAILURE",
|
|
4464
|
+
"The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
|
|
4465
|
+
{
|
|
4466
|
+
txHash,
|
|
4467
|
+
fundsSubmitted: true,
|
|
4468
|
+
agentResults,
|
|
4469
|
+
failedAgentIds: failures
|
|
4470
|
+
}
|
|
4471
|
+
);
|
|
4472
|
+
return { agentResults };
|
|
4473
|
+
} catch (error) {
|
|
4474
|
+
funding.reject(error);
|
|
4475
|
+
await Promise.allSettled(tasks);
|
|
4476
|
+
throw error;
|
|
4477
|
+
}
|
|
4406
4478
|
}
|
|
4407
4479
|
|
|
4408
4480
|
// src/lib/sponsored-calls-deposit.ts
|
|
@@ -4432,7 +4504,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4432
4504
|
}
|
|
4433
4505
|
return new URL(configured, origin).toString();
|
|
4434
4506
|
};
|
|
4435
|
-
|
|
4507
|
+
const batch = async (chainId, transfers) => {
|
|
4436
4508
|
const cid = chainId;
|
|
4437
4509
|
const token = deps.tokenAddressByChain[cid];
|
|
4438
4510
|
if (!token) {
|
|
@@ -4448,19 +4520,24 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4448
4520
|
{ chainId }
|
|
4449
4521
|
);
|
|
4450
4522
|
}
|
|
4451
|
-
const
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4523
|
+
const calls = transfers.map((transfer) => ({
|
|
4524
|
+
to: token,
|
|
4525
|
+
value: "0x0",
|
|
4526
|
+
data: encodeFunctionData2({
|
|
4527
|
+
abi: erc20Abi2,
|
|
4528
|
+
functionName: "transfer",
|
|
4529
|
+
args: [transfer.to, BigInt(transfer.amount)]
|
|
4530
|
+
})
|
|
4531
|
+
}));
|
|
4456
4532
|
let paymasterUrl = absolutePaymasterUrl();
|
|
4457
|
-
|
|
4458
|
-
|
|
4533
|
+
for (const transfer of transfers) {
|
|
4534
|
+
const verification = transfer.yieldseeker;
|
|
4535
|
+
if (!verification) continue;
|
|
4536
|
+
if (chainId !== 8453)
|
|
4459
4537
|
throw new OwneyError(
|
|
4460
4538
|
"CHAIN_UNSUPPORTED",
|
|
4461
|
-
`Yieldseeker
|
|
4539
|
+
`Yieldseeker sponsorship is not available on chain ${chainId}.`
|
|
4462
4540
|
);
|
|
4463
|
-
}
|
|
4464
4541
|
const { intent } = await postPaymasterIntent({
|
|
4465
4542
|
baseUrl: deps.routingApiBaseUrl,
|
|
4466
4543
|
apiKey: deps.apiKey,
|
|
@@ -4469,14 +4546,14 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4469
4546
|
chainId,
|
|
4470
4547
|
token,
|
|
4471
4548
|
from: deps.ownerAddress,
|
|
4472
|
-
to:
|
|
4473
|
-
amount,
|
|
4549
|
+
to: transfer.to,
|
|
4550
|
+
amount: transfer.amount,
|
|
4474
4551
|
yieldseekerUserId: verification.userId,
|
|
4475
|
-
yieldseekerAgentId: verification.
|
|
4552
|
+
yieldseekerAgentId: verification.agentId
|
|
4476
4553
|
}
|
|
4477
4554
|
});
|
|
4478
4555
|
const url = new URL(paymasterUrl);
|
|
4479
|
-
url.searchParams.
|
|
4556
|
+
url.searchParams.append("owneyIntent", intent);
|
|
4480
4557
|
paymasterUrl = url.toString();
|
|
4481
4558
|
}
|
|
4482
4559
|
const sendResult = await deps.provider.request({
|
|
@@ -4486,8 +4563,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4486
4563
|
version: "2.0.0",
|
|
4487
4564
|
from: deps.ownerAddress,
|
|
4488
4565
|
chainId: toHex(chainId),
|
|
4489
|
-
atomicRequired:
|
|
4490
|
-
calls
|
|
4566
|
+
atomicRequired: transfers.length > 1,
|
|
4567
|
+
calls,
|
|
4491
4568
|
capabilities: {
|
|
4492
4569
|
paymasterService: { url: paymasterUrl }
|
|
4493
4570
|
}
|
|
@@ -4509,7 +4586,24 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4509
4586
|
params: [callsId]
|
|
4510
4587
|
});
|
|
4511
4588
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
4512
|
-
if (
|
|
4589
|
+
if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
|
|
4590
|
+
throw new OwneyError(
|
|
4591
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4592
|
+
"The sponsored deposit did not complete successfully.",
|
|
4593
|
+
{ chainId, callsId, safeToFallback: false }
|
|
4594
|
+
);
|
|
4595
|
+
}
|
|
4596
|
+
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
4597
|
+
if (status?.receipts?.some(
|
|
4598
|
+
(receipt) => receipt.transactionHash !== txHash
|
|
4599
|
+
))
|
|
4600
|
+
throw new OwneyError(
|
|
4601
|
+
"SPONSORED_CALLS_NO_RECEIPT",
|
|
4602
|
+
"The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
|
|
4603
|
+
{ chainId, callsId }
|
|
4604
|
+
);
|
|
4605
|
+
return txHash;
|
|
4606
|
+
}
|
|
4513
4607
|
if (pollIntervalMs > 0) {
|
|
4514
4608
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
4515
4609
|
}
|
|
@@ -4519,7 +4613,12 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4519
4613
|
`No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
|
|
4520
4614
|
{ chainId, callsId }
|
|
4521
4615
|
);
|
|
4522
|
-
}
|
|
4616
|
+
};
|
|
4617
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4618
|
+
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
4619
|
+
);
|
|
4620
|
+
registerDepositBatch(callback, batch);
|
|
4621
|
+
return callback;
|
|
4523
4622
|
}
|
|
4524
4623
|
|
|
4525
4624
|
// src/client.ts
|
|
@@ -4558,6 +4657,11 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
4558
4657
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
4559
4658
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
4560
4659
|
};
|
|
4660
|
+
var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
|
|
4661
|
+
function sponsoredTokensFor(asset) {
|
|
4662
|
+
if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
|
|
4663
|
+
return SPONSORED_TOKENS_BY_ASSET[asset];
|
|
4664
|
+
}
|
|
4561
4665
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
4562
4666
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
4563
4667
|
}
|
|
@@ -4692,18 +4796,13 @@ var OwneySDK = class {
|
|
|
4692
4796
|
}
|
|
4693
4797
|
return this.state.provider;
|
|
4694
4798
|
}
|
|
4695
|
-
/**
|
|
4696
|
-
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
4697
|
-
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
4698
|
-
* provider with viem `custom(provider)` to read token meta and sign the
|
|
4699
|
-
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
4700
|
-
*/
|
|
4799
|
+
/** Builds the default USDC batch callback for the connected wallet. */
|
|
4701
4800
|
getDefaultSponsoredCallback(onApproved) {
|
|
4702
4801
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
4703
4802
|
return this.cachedSponsoredCallback;
|
|
4704
4803
|
const provider = this.requireConnectedProvider();
|
|
4705
4804
|
const owner = this.state.walletAddress;
|
|
4706
|
-
const callback =
|
|
4805
|
+
const callback = makeSponsoredTokenCallback({
|
|
4707
4806
|
apiKey: this.apiKey,
|
|
4708
4807
|
baseUrl: this.routingApiBaseUrl,
|
|
4709
4808
|
ownerAddress: owner,
|
|
@@ -4725,12 +4824,7 @@ var OwneySDK = class {
|
|
|
4725
4824
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
4726
4825
|
return callback;
|
|
4727
4826
|
}
|
|
4728
|
-
/**
|
|
4729
|
-
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
4730
|
-
* callback used when the caller omits `depositCallback` for a WETH
|
|
4731
|
-
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
4732
|
-
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
4733
|
-
*/
|
|
4827
|
+
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
4734
4828
|
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
4735
4829
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
4736
4830
|
if (!onApproved && cached) return cached;
|
|
@@ -4742,7 +4836,7 @@ var OwneySDK = class {
|
|
|
4742
4836
|
ownerAddress: this.state.walletAddress,
|
|
4743
4837
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
4744
4838
|
onApproved,
|
|
4745
|
-
tokenAddressByChain: asset
|
|
4839
|
+
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
4746
4840
|
});
|
|
4747
4841
|
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
4748
4842
|
return callback;
|
|
@@ -4751,14 +4845,14 @@ var OwneySDK = class {
|
|
|
4751
4845
|
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
4752
4846
|
* callback used when the caller omits `depositCallback` for a WETH deposit.
|
|
4753
4847
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
4754
|
-
*
|
|
4848
|
+
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
4755
4849
|
*/
|
|
4756
4850
|
getDefaultWethSponsoredCallback(onApproved) {
|
|
4757
4851
|
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
4758
4852
|
return this.cachedWethSponsoredCallback;
|
|
4759
4853
|
const provider = this.requireConnectedProvider();
|
|
4760
4854
|
const owner = this.state.walletAddress;
|
|
4761
|
-
const callback =
|
|
4855
|
+
const callback = makeSponsoredTokenCallback({
|
|
4762
4856
|
apiKey: this.apiKey,
|
|
4763
4857
|
baseUrl: this.routingApiBaseUrl,
|
|
4764
4858
|
ownerAddress: owner,
|
|
@@ -5064,7 +5158,8 @@ var OwneySDK = class {
|
|
|
5064
5158
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
5065
5159
|
* @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
|
|
5066
5160
|
* When agentId is omitted, this callback is invoked once per eligible agent with that agent's
|
|
5067
|
-
* split amount and smart wallet address
|
|
5161
|
+
* split amount and smart wallet address. Default sponsored deposits batch
|
|
5162
|
+
* all shares into one signature; custom callbacks still run once per agent.
|
|
5068
5163
|
* @param options.agentId - Optional explicit target. Otherwise split equally,
|
|
5069
5164
|
* or fund remaining agents when a recovery deposit cannot meet every minimum.
|
|
5070
5165
|
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
@@ -5149,6 +5244,39 @@ var OwneySDK = class {
|
|
|
5149
5244
|
}
|
|
5150
5245
|
);
|
|
5151
5246
|
}
|
|
5247
|
+
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
5248
|
+
if (!depositCallback && batchTransfer) {
|
|
5249
|
+
return runAgentDepositBatch(
|
|
5250
|
+
chainId,
|
|
5251
|
+
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
5252
|
+
id: agent.id,
|
|
5253
|
+
amount: amount2,
|
|
5254
|
+
run: (callback) => withFailureReporting(
|
|
5255
|
+
this.apiKey,
|
|
5256
|
+
agent.id,
|
|
5257
|
+
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
5258
|
+
this.routingApiBaseUrl
|
|
5259
|
+
)
|
|
5260
|
+
})),
|
|
5261
|
+
async (cid, transfers) => {
|
|
5262
|
+
try {
|
|
5263
|
+
return await batchTransfer(cid, transfers);
|
|
5264
|
+
} catch (error) {
|
|
5265
|
+
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
5266
|
+
throw error;
|
|
5267
|
+
const requiredAmount = transfers.reduce(
|
|
5268
|
+
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
5269
|
+
0n
|
|
5270
|
+
);
|
|
5271
|
+
await this.approvePermit2(
|
|
5272
|
+
asset,
|
|
5273
|
+
requiredAmount
|
|
5274
|
+
);
|
|
5275
|
+
return batchTransfer(cid, transfers);
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
);
|
|
5279
|
+
}
|
|
5152
5280
|
const agentResults = {};
|
|
5153
5281
|
for (const [
|
|
5154
5282
|
index,
|
|
@@ -5188,7 +5316,7 @@ var OwneySDK = class {
|
|
|
5188
5316
|
*
|
|
5189
5317
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
5190
5318
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
5191
|
-
*
|
|
5319
|
+
* token deposit, this is the wallet's first Permit2 deposit for that token. We send
|
|
5192
5320
|
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
5193
5321
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5194
5322
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
@@ -5225,12 +5353,15 @@ var OwneySDK = class {
|
|
|
5225
5353
|
try {
|
|
5226
5354
|
return await attempt(effectiveCallback);
|
|
5227
5355
|
} catch (error) {
|
|
5228
|
-
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
5356
|
+
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
5229
5357
|
approvalAttempted = true;
|
|
5230
5358
|
console.warn(
|
|
5231
|
-
"[owney-sdk] First
|
|
5359
|
+
"[owney-sdk] First token deposit: sending one-time Permit2 approval..."
|
|
5360
|
+
);
|
|
5361
|
+
await this.approvePermit2(
|
|
5362
|
+
asset,
|
|
5363
|
+
BigInt(amount)
|
|
5232
5364
|
);
|
|
5233
|
-
await this.approvePermit2();
|
|
5234
5365
|
continue;
|
|
5235
5366
|
}
|
|
5236
5367
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -5883,26 +6014,31 @@ var OwneySDK = class {
|
|
|
5883
6014
|
return pending;
|
|
5884
6015
|
}
|
|
5885
6016
|
/**
|
|
5886
|
-
*
|
|
5887
|
-
*
|
|
5888
|
-
*
|
|
5889
|
-
*
|
|
5890
|
-
*
|
|
6017
|
+
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6018
|
+
* Approves exactly the pending deposit amount. Another approval is required
|
|
6019
|
+
* for a later deposit once this allowance has been consumed. Resolves after
|
|
6020
|
+
* one confirmation so the subsequent deposit attempt sees the new allowance.
|
|
6021
|
+
*
|
|
6022
|
+
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
5891
6023
|
* @returns the approval transaction hash.
|
|
5892
6024
|
*/
|
|
5893
|
-
async approvePermit2(asset = "WETH") {
|
|
5894
|
-
void asset;
|
|
6025
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
5895
6026
|
const state = this.requireState();
|
|
5896
6027
|
const chainId = this.requireChainId();
|
|
5897
|
-
this.
|
|
5898
|
-
const token =
|
|
6028
|
+
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6029
|
+
const token = sponsoredTokensFor(asset)[chainId];
|
|
5899
6030
|
if (!token) {
|
|
5900
6031
|
throw new OwneyError(
|
|
5901
6032
|
"CHAIN_UNSUPPORTED",
|
|
5902
|
-
`No sponsored
|
|
6033
|
+
`No sponsored token on chain ${chainId}`
|
|
5903
6034
|
);
|
|
5904
6035
|
}
|
|
5905
6036
|
const provider = this.requireConnectedProvider();
|
|
6037
|
+
const publicClient = createPublicClient4({
|
|
6038
|
+
chain: VIEM_CHAIN2[chainId],
|
|
6039
|
+
transport: custom3(provider)
|
|
6040
|
+
});
|
|
6041
|
+
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
5906
6042
|
const wallet = createWalletClient3({
|
|
5907
6043
|
account: state.walletAddress,
|
|
5908
6044
|
chain: VIEM_CHAIN2[chainId],
|
|
@@ -5912,14 +6048,10 @@ var OwneySDK = class {
|
|
|
5912
6048
|
address: token,
|
|
5913
6049
|
abi: ERC20_ALLOWANCE_ABI,
|
|
5914
6050
|
functionName: "approve",
|
|
5915
|
-
args: [PERMIT2_ADDRESS,
|
|
6051
|
+
args: [PERMIT2_ADDRESS, approvalAmount],
|
|
5916
6052
|
account: state.walletAddress,
|
|
5917
6053
|
chain: VIEM_CHAIN2[chainId]
|
|
5918
6054
|
});
|
|
5919
|
-
const publicClient = createPublicClient4({
|
|
5920
|
-
chain: VIEM_CHAIN2[chainId],
|
|
5921
|
-
transport: custom3(provider)
|
|
5922
|
-
});
|
|
5923
6055
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
5924
6056
|
hash,
|
|
5925
6057
|
confirmations: 1
|