@owney/sdk 0.7.5 → 0.7.7-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 +155 -44
- package/dist/index.d.cts +23 -12
- package/dist/index.d.ts +23 -12
- package/dist/index.js +157 -46
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -825,6 +825,9 @@ function debugLog(scope, message, data) {
|
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
// src/agents/zyfai/zyfai.agent.ts
|
|
828
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
|
|
829
|
+
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
830
|
+
]);
|
|
828
831
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
829
832
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
830
833
|
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
@@ -1142,7 +1145,12 @@ var ZyfaiAgent = class {
|
|
|
1142
1145
|
const validChainId = isValidChainId(chainId);
|
|
1143
1146
|
await Promise.all([
|
|
1144
1147
|
this.ensureProtocolsSynced(validChainId, "USDC"),
|
|
1145
|
-
this.ensureProtocolsSynced(validChainId, "WETH")
|
|
1148
|
+
this.ensureProtocolsSynced(validChainId, "WETH"),
|
|
1149
|
+
// Retrofit a session key for old wallets that are already deployed and
|
|
1150
|
+
// funded but never got one — otherwise their balance stays idle until
|
|
1151
|
+
// they happen to deposit again. Only touches Safes that are missing the
|
|
1152
|
+
// validator, so pool-wallet users see no prompt. Non-fatal.
|
|
1153
|
+
this.ensureSessionKeyIfDeployed(validChainId)
|
|
1146
1154
|
]);
|
|
1147
1155
|
} catch (error) {
|
|
1148
1156
|
throw error;
|
|
@@ -1340,6 +1348,65 @@ var ZyfaiAgent = class {
|
|
|
1340
1348
|
return false;
|
|
1341
1349
|
}
|
|
1342
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Ensure Zyfai's automation can actually act on the user's Safe for this
|
|
1353
|
+
* chain, by making sure a session key exists.
|
|
1354
|
+
*
|
|
1355
|
+
* Old wallets (pre-0.2.44) are deployed WITHOUT the smart-sessions validator,
|
|
1356
|
+
* so Zyfai's rebalancer has no authorised path to move funds: deposits land
|
|
1357
|
+
* and then sit as raw tokens forever. Pre-deployed pool wallets ship with the
|
|
1358
|
+
* module already installed, which is why they never needed this.
|
|
1359
|
+
*
|
|
1360
|
+
* The gate is the on-chain module, not the wallet's class: it is the exact
|
|
1361
|
+
* condition that matters, it needs no guessing from API shapes, and it means
|
|
1362
|
+
* pool-wallet users are never prompted for a signature they don't need.
|
|
1363
|
+
*
|
|
1364
|
+
* Non-fatal by design. A failure here must not block a deposit — the funds
|
|
1365
|
+
* still arrive safely in the Safe, they just won't be deployed into a pool
|
|
1366
|
+
* until a session key exists.
|
|
1367
|
+
*/
|
|
1368
|
+
/**
|
|
1369
|
+
* Activation-time variant: only acts when the Safe already has code on this
|
|
1370
|
+
* chain. Activation must never deploy anything — that is deposit's job, and it
|
|
1371
|
+
* would mean surprise on-chain work just for opening the app.
|
|
1372
|
+
*/
|
|
1373
|
+
async ensureSessionKeyIfDeployed(chainId) {
|
|
1374
|
+
try {
|
|
1375
|
+
const ownerAddress = this.getAddress();
|
|
1376
|
+
const wallet = await this.sdk.getSmartWalletAddress(
|
|
1377
|
+
ownerAddress,
|
|
1378
|
+
chainId
|
|
1379
|
+
);
|
|
1380
|
+
if (!wallet.isDeployed) return;
|
|
1381
|
+
await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
console.warn(
|
|
1384
|
+
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
1385
|
+
error instanceof Error ? error.message : String(error)
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1390
|
+
try {
|
|
1391
|
+
const installed = await this.getPublicClient(chainId).readContract({
|
|
1392
|
+
address: smartWallet,
|
|
1393
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1394
|
+
functionName: "isModuleInstalled",
|
|
1395
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1396
|
+
args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1397
|
+
});
|
|
1398
|
+
if (installed) return;
|
|
1399
|
+
console.warn(
|
|
1400
|
+
`[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
|
|
1401
|
+
);
|
|
1402
|
+
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
console.warn(
|
|
1405
|
+
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
1406
|
+
error instanceof Error ? error.message : String(error)
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1343
1410
|
/**
|
|
1344
1411
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1345
1412
|
* funds into it, and return its address.
|
|
@@ -1360,17 +1427,21 @@ var ZyfaiAgent = class {
|
|
|
1360
1427
|
*/
|
|
1361
1428
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1362
1429
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1363
|
-
if (wallet.isDeployed)
|
|
1430
|
+
if (wallet.isDeployed) {
|
|
1431
|
+
await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
|
|
1432
|
+
return wallet.address;
|
|
1433
|
+
}
|
|
1364
1434
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
1365
1435
|
ownerAddress,
|
|
1366
1436
|
wallet.address
|
|
1367
1437
|
);
|
|
1368
1438
|
const deployAsPool = () => this.sdk.deployOnChains([chainId]);
|
|
1369
1439
|
const deployAsLegacy = () => (
|
|
1370
|
-
// `createSessionKey:
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
1373
|
-
|
|
1440
|
+
// `createSessionKey: true` — per Zyfai, the old-wallet flow is
|
|
1441
|
+
// deploySafe(true) -> createSessionKey() -> deposit. Passing false (which
|
|
1442
|
+
// this did originally) deploys a Safe with no smart-sessions validator, so
|
|
1443
|
+
// deposits land and are never allocated into a pool.
|
|
1444
|
+
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1374
1445
|
);
|
|
1375
1446
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1376
1447
|
try {
|
|
@@ -1394,6 +1465,7 @@ var ZyfaiAgent = class {
|
|
|
1394
1465
|
this.id
|
|
1395
1466
|
);
|
|
1396
1467
|
}
|
|
1468
|
+
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
1397
1469
|
return redeployed.address;
|
|
1398
1470
|
}
|
|
1399
1471
|
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
@@ -1840,9 +1912,12 @@ async function postSponsorTransferAuth(input) {
|
|
|
1840
1912
|
{
|
|
1841
1913
|
statusCode: res.status,
|
|
1842
1914
|
responseBody: text.slice(0, 500),
|
|
1843
|
-
//
|
|
1844
|
-
//
|
|
1845
|
-
|
|
1915
|
+
// A 4xx is an explicit refusal before execution (DTO validation,
|
|
1916
|
+
// signature rejection, simulation) — nothing was broadcast, so a
|
|
1917
|
+
// user-paid retry cannot double-spend. 503 SPONSOR_UNAVAILABLE means
|
|
1918
|
+
// the relayer was out of gas, also pre-broadcast. Any other 5xx stays
|
|
1919
|
+
// ambiguous: the transfer may already be in flight.
|
|
1920
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
1846
1921
|
}
|
|
1847
1922
|
);
|
|
1848
1923
|
}
|
|
@@ -2114,9 +2189,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2114
2189
|
};
|
|
2115
2190
|
}
|
|
2116
2191
|
|
|
2117
|
-
// src/lib/sponsored-
|
|
2192
|
+
// src/lib/sponsored-permit2-deposit.ts
|
|
2118
2193
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2119
|
-
function
|
|
2194
|
+
function makeSponsoredPermit2Callback(deps) {
|
|
2120
2195
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2121
2196
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2122
2197
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2125,7 +2200,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2125
2200
|
if (!token) {
|
|
2126
2201
|
throw new OwneyError(
|
|
2127
2202
|
"CHAIN_UNSUPPORTED",
|
|
2128
|
-
`No sponsored
|
|
2203
|
+
`No sponsored ${deps.assetLabel} configured for chain ${chainId}`
|
|
2129
2204
|
);
|
|
2130
2205
|
}
|
|
2131
2206
|
const amountWei = BigInt(amount);
|
|
@@ -2137,14 +2212,14 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2137
2212
|
if (balance < amountWei) {
|
|
2138
2213
|
throw new OwneyError(
|
|
2139
2214
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2140
|
-
|
|
2215
|
+
`Insufficient ${deps.assetLabel} balance for this deposit.`,
|
|
2141
2216
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2142
2217
|
);
|
|
2143
2218
|
}
|
|
2144
2219
|
} catch (err) {
|
|
2145
2220
|
if (err instanceof OwneyError) throw err;
|
|
2146
2221
|
console.warn(
|
|
2147
|
-
|
|
2222
|
+
`[owney-sdk] ${deps.assetLabel} balance pre-check failed (non-fatal):`,
|
|
2148
2223
|
err instanceof Error ? err.message : String(err)
|
|
2149
2224
|
);
|
|
2150
2225
|
}
|
|
@@ -2152,7 +2227,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2152
2227
|
if (allowance < amountWei) {
|
|
2153
2228
|
throw new OwneyError(
|
|
2154
2229
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2155
|
-
|
|
2230
|
+
`${deps.assetLabel} gasless deposits need a one-time Permit2 approval; call approvePermit2("${deps.assetLabel}") first`,
|
|
2156
2231
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2157
2232
|
);
|
|
2158
2233
|
}
|
|
@@ -2257,7 +2332,7 @@ var OwneySDK = class {
|
|
|
2257
2332
|
routingApiBaseUrl;
|
|
2258
2333
|
referralSource;
|
|
2259
2334
|
cachedSponsoredCallback = null;
|
|
2260
|
-
|
|
2335
|
+
cachedPermit2Callbacks = /* @__PURE__ */ new Map();
|
|
2261
2336
|
initializingAgentsPromise = null;
|
|
2262
2337
|
constructor(config) {
|
|
2263
2338
|
this.apiKey = config.apiKey;
|
|
@@ -2285,7 +2360,7 @@ var OwneySDK = class {
|
|
|
2285
2360
|
}
|
|
2286
2361
|
this.state = { provider, walletAddress, chainId: null };
|
|
2287
2362
|
this.cachedSponsoredCallback = null;
|
|
2288
|
-
this.
|
|
2363
|
+
this.cachedPermit2Callbacks.clear();
|
|
2289
2364
|
}
|
|
2290
2365
|
/**
|
|
2291
2366
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2299,7 +2374,7 @@ var OwneySDK = class {
|
|
|
2299
2374
|
this.disabledAgents.clear();
|
|
2300
2375
|
this.state = null;
|
|
2301
2376
|
this.cachedSponsoredCallback = null;
|
|
2302
|
-
this.
|
|
2377
|
+
this.cachedPermit2Callbacks.clear();
|
|
2303
2378
|
}
|
|
2304
2379
|
isAgentDisabled(agentId) {
|
|
2305
2380
|
return this.disabledAgents.has(agentId);
|
|
@@ -2376,25 +2451,23 @@ var OwneySDK = class {
|
|
|
2376
2451
|
return callback;
|
|
2377
2452
|
}
|
|
2378
2453
|
/**
|
|
2379
|
-
* Lazily builds (and caches) the
|
|
2380
|
-
*
|
|
2381
|
-
*
|
|
2382
|
-
*
|
|
2454
|
+
* Lazily builds (and caches) the Permit2 sponsored callback for one asset.
|
|
2455
|
+
* Used for WETH always, and for USDC when the connected owner is a smart
|
|
2456
|
+
* account — EIP-3009 verifies with ecrecover inside USDC and cannot accept a
|
|
2457
|
+
* contract signature, while Permit2 supports ERC-1271.
|
|
2383
2458
|
*/
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2459
|
+
getDefaultPermit2Callback(asset, onApproved) {
|
|
2460
|
+
const cached = this.cachedPermit2Callbacks.get(asset);
|
|
2461
|
+
if (!onApproved && cached) return cached;
|
|
2387
2462
|
const provider = this.requireConnectedProvider();
|
|
2388
2463
|
const owner = this.state.walletAddress;
|
|
2389
|
-
const callback =
|
|
2464
|
+
const callback = makeSponsoredPermit2Callback({
|
|
2390
2465
|
apiKey: this.apiKey,
|
|
2391
2466
|
baseUrl: this.routingApiBaseUrl,
|
|
2392
2467
|
ownerAddress: owner,
|
|
2468
|
+
assetLabel: asset,
|
|
2393
2469
|
onApproved,
|
|
2394
|
-
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2395
|
-
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2396
|
-
// PublicClient/WalletClient param types — structurally identical at
|
|
2397
|
-
// runtime, but the two share a name TS treats as unrelated.
|
|
2470
|
+
tokenAddressByChain: asset === "USDC" ? SPONSORED_USDC_BY_CHAIN : SPONSORED_WETH_BY_CHAIN,
|
|
2398
2471
|
getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
|
|
2399
2472
|
chain: VIEM_CHAIN2[cid],
|
|
2400
2473
|
transport: (0, import_viem5.custom)(provider)
|
|
@@ -2405,9 +2478,34 @@ var OwneySDK = class {
|
|
|
2405
2478
|
transport: (0, import_viem5.custom)(provider)
|
|
2406
2479
|
})
|
|
2407
2480
|
});
|
|
2408
|
-
if (!onApproved) this.
|
|
2481
|
+
if (!onApproved) this.cachedPermit2Callbacks.set(asset, callback);
|
|
2409
2482
|
return callback;
|
|
2410
2483
|
}
|
|
2484
|
+
/**
|
|
2485
|
+
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
2486
|
+
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
2487
|
+
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
2488
|
+
*
|
|
2489
|
+
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
2490
|
+
* is never blocked by a flaky provider.
|
|
2491
|
+
*/
|
|
2492
|
+
async ownerHasCode() {
|
|
2493
|
+
try {
|
|
2494
|
+
const provider = this.requireConnectedProvider();
|
|
2495
|
+
const code = await provider.request({
|
|
2496
|
+
method: "eth_getCode",
|
|
2497
|
+
params: [this.state.walletAddress, "latest"]
|
|
2498
|
+
});
|
|
2499
|
+
return Boolean(code) && code !== "0x";
|
|
2500
|
+
} catch (error) {
|
|
2501
|
+
debugLog(
|
|
2502
|
+
"deposit",
|
|
2503
|
+
"owner code check failed; assuming EOA",
|
|
2504
|
+
error instanceof Error ? error.message : String(error)
|
|
2505
|
+
);
|
|
2506
|
+
return false;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2411
2509
|
getAgent(agentId) {
|
|
2412
2510
|
const agent = this.agents.get(agentId);
|
|
2413
2511
|
if (!agent) {
|
|
@@ -2603,7 +2701,11 @@ var OwneySDK = class {
|
|
|
2603
2701
|
}
|
|
2604
2702
|
const state = this.requireState();
|
|
2605
2703
|
const chainId = this.requireChainId();
|
|
2606
|
-
const
|
|
2704
|
+
const ownerIsSmartAccount = depositCallback === void 0 && asset !== "WETH" ? await this.ownerHasCode() : false;
|
|
2705
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" || ownerIsSmartAccount ? this.getDefaultPermit2Callback(
|
|
2706
|
+
asset === "WETH" ? "WETH" : "USDC",
|
|
2707
|
+
onApproved
|
|
2708
|
+
) : this.getDefaultSponsoredCallback(onApproved));
|
|
2607
2709
|
if (agentId) {
|
|
2608
2710
|
if (this.isAgentDisabled(agentId)) {
|
|
2609
2711
|
throw new OwneyError(
|
|
@@ -2707,12 +2809,12 @@ var OwneySDK = class {
|
|
|
2707
2809
|
try {
|
|
2708
2810
|
return await attempt(effectiveCallback);
|
|
2709
2811
|
} catch (error) {
|
|
2710
|
-
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2812
|
+
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2711
2813
|
approvalAttempted = true;
|
|
2712
2814
|
console.warn(
|
|
2713
|
-
|
|
2815
|
+
`[owney-sdk] First ${asset} gasless deposit: sending one-time Permit2 approval...`
|
|
2714
2816
|
);
|
|
2715
|
-
await this.approvePermit2();
|
|
2817
|
+
await this.approvePermit2(asset);
|
|
2716
2818
|
continue;
|
|
2717
2819
|
}
|
|
2718
2820
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3226,23 +3328,32 @@ var OwneySDK = class {
|
|
|
3226
3328
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3227
3329
|
}
|
|
3228
3330
|
/**
|
|
3229
|
-
* One-time, user-paid approval of Permit2 on the sponsored
|
|
3230
|
-
* the active chain. Required once per wallet per chain before
|
|
3231
|
-
*
|
|
3232
|
-
*
|
|
3233
|
-
*
|
|
3331
|
+
* One-time, user-paid approval of Permit2 on the given sponsored token for
|
|
3332
|
+
* the active chain. Required once per wallet per chain per token before a
|
|
3333
|
+
* gasless deposit of that asset over Permit2; afterwards deposit() is
|
|
3334
|
+
* signature-only for it. This call itself is a real transaction the user
|
|
3335
|
+
* pays gas for. Resolves only after the approval transaction is mined (1
|
|
3336
|
+
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
3337
|
+
* throws if the transaction reverted.
|
|
3234
3338
|
* @returns the approval transaction hash.
|
|
3235
3339
|
*/
|
|
3236
3340
|
async approvePermit2(asset = "WETH") {
|
|
3237
|
-
void asset;
|
|
3238
3341
|
const state = this.requireState();
|
|
3239
3342
|
const chainId = this.requireChainId();
|
|
3240
|
-
this.
|
|
3241
|
-
|
|
3343
|
+
let guard = this.agents.get("zyfai");
|
|
3344
|
+
if (!guard) {
|
|
3345
|
+
try {
|
|
3346
|
+
guard = this.getEligibleAgents(chainId, asset)[0];
|
|
3347
|
+
} catch {
|
|
3348
|
+
guard = void 0;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
if (guard) this.validateAssetSupport(guard, chainId, asset);
|
|
3352
|
+
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3242
3353
|
if (!token) {
|
|
3243
3354
|
throw new OwneyError(
|
|
3244
3355
|
"CHAIN_UNSUPPORTED",
|
|
3245
|
-
`No sponsored
|
|
3356
|
+
`No sponsored ${asset} on chain ${chainId}`
|
|
3246
3357
|
);
|
|
3247
3358
|
}
|
|
3248
3359
|
const provider = this.requireConnectedProvider();
|
package/dist/index.d.cts
CHANGED
|
@@ -392,7 +392,7 @@ declare class OwneySDK {
|
|
|
392
392
|
private routingApiBaseUrl?;
|
|
393
393
|
private referralSource?;
|
|
394
394
|
private cachedSponsoredCallback;
|
|
395
|
-
private
|
|
395
|
+
private cachedPermit2Callbacks;
|
|
396
396
|
private initializingAgentsPromise;
|
|
397
397
|
constructor(config: OwneySDKConfig);
|
|
398
398
|
/**
|
|
@@ -428,12 +428,21 @@ declare class OwneySDK {
|
|
|
428
428
|
*/
|
|
429
429
|
private getDefaultSponsoredCallback;
|
|
430
430
|
/**
|
|
431
|
-
* Lazily builds (and caches) the
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
431
|
+
* Lazily builds (and caches) the Permit2 sponsored callback for one asset.
|
|
432
|
+
* Used for WETH always, and for USDC when the connected owner is a smart
|
|
433
|
+
* account — EIP-3009 verifies with ecrecover inside USDC and cannot accept a
|
|
434
|
+
* contract signature, while Permit2 supports ERC-1271.
|
|
435
435
|
*/
|
|
436
|
-
private
|
|
436
|
+
private getDefaultPermit2Callback;
|
|
437
|
+
/**
|
|
438
|
+
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
439
|
+
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
440
|
+
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
441
|
+
*
|
|
442
|
+
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
443
|
+
* is never blocked by a flaky provider.
|
|
444
|
+
*/
|
|
445
|
+
private ownerHasCode;
|
|
437
446
|
private getAgent;
|
|
438
447
|
private getActiveAgents;
|
|
439
448
|
/**
|
|
@@ -578,14 +587,16 @@ declare class OwneySDK {
|
|
|
578
587
|
*/
|
|
579
588
|
ensureAutoSelectProtocols(asset: "USDC" | "WETH", agentId?: AgentId): Promise<boolean>;
|
|
580
589
|
/**
|
|
581
|
-
* One-time, user-paid approval of Permit2 on the sponsored
|
|
582
|
-
* the active chain. Required once per wallet per chain before
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
590
|
+
* One-time, user-paid approval of Permit2 on the given sponsored token for
|
|
591
|
+
* the active chain. Required once per wallet per chain per token before a
|
|
592
|
+
* gasless deposit of that asset over Permit2; afterwards deposit() is
|
|
593
|
+
* signature-only for it. This call itself is a real transaction the user
|
|
594
|
+
* pays gas for. Resolves only after the approval transaction is mined (1
|
|
595
|
+
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
596
|
+
* throws if the transaction reverted.
|
|
586
597
|
* @returns the approval transaction hash.
|
|
587
598
|
*/
|
|
588
|
-
approvePermit2(asset?: "WETH"): Promise<`0x${string}`>;
|
|
599
|
+
approvePermit2(asset?: "WETH" | "USDC"): Promise<`0x${string}`>;
|
|
589
600
|
/**
|
|
590
601
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
591
602
|
* @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
|
package/dist/index.d.ts
CHANGED
|
@@ -392,7 +392,7 @@ declare class OwneySDK {
|
|
|
392
392
|
private routingApiBaseUrl?;
|
|
393
393
|
private referralSource?;
|
|
394
394
|
private cachedSponsoredCallback;
|
|
395
|
-
private
|
|
395
|
+
private cachedPermit2Callbacks;
|
|
396
396
|
private initializingAgentsPromise;
|
|
397
397
|
constructor(config: OwneySDKConfig);
|
|
398
398
|
/**
|
|
@@ -428,12 +428,21 @@ declare class OwneySDK {
|
|
|
428
428
|
*/
|
|
429
429
|
private getDefaultSponsoredCallback;
|
|
430
430
|
/**
|
|
431
|
-
* Lazily builds (and caches) the
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
431
|
+
* Lazily builds (and caches) the Permit2 sponsored callback for one asset.
|
|
432
|
+
* Used for WETH always, and for USDC when the connected owner is a smart
|
|
433
|
+
* account — EIP-3009 verifies with ecrecover inside USDC and cannot accept a
|
|
434
|
+
* contract signature, while Permit2 supports ERC-1271.
|
|
435
435
|
*/
|
|
436
|
-
private
|
|
436
|
+
private getDefaultPermit2Callback;
|
|
437
|
+
/**
|
|
438
|
+
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
439
|
+
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
440
|
+
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
441
|
+
*
|
|
442
|
+
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
443
|
+
* is never blocked by a flaky provider.
|
|
444
|
+
*/
|
|
445
|
+
private ownerHasCode;
|
|
437
446
|
private getAgent;
|
|
438
447
|
private getActiveAgents;
|
|
439
448
|
/**
|
|
@@ -578,14 +587,16 @@ declare class OwneySDK {
|
|
|
578
587
|
*/
|
|
579
588
|
ensureAutoSelectProtocols(asset: "USDC" | "WETH", agentId?: AgentId): Promise<boolean>;
|
|
580
589
|
/**
|
|
581
|
-
* One-time, user-paid approval of Permit2 on the sponsored
|
|
582
|
-
* the active chain. Required once per wallet per chain before
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
590
|
+
* One-time, user-paid approval of Permit2 on the given sponsored token for
|
|
591
|
+
* the active chain. Required once per wallet per chain per token before a
|
|
592
|
+
* gasless deposit of that asset over Permit2; afterwards deposit() is
|
|
593
|
+
* signature-only for it. This call itself is a real transaction the user
|
|
594
|
+
* pays gas for. Resolves only after the approval transaction is mined (1
|
|
595
|
+
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
596
|
+
* throws if the transaction reverted.
|
|
586
597
|
* @returns the approval transaction hash.
|
|
587
598
|
*/
|
|
588
|
-
approvePermit2(asset?: "WETH"): Promise<`0x${string}`>;
|
|
599
|
+
approvePermit2(asset?: "WETH" | "USDC"): Promise<`0x${string}`>;
|
|
589
600
|
/**
|
|
590
601
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
591
602
|
* @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/agents/zyfai/zyfai.agent.ts
|
|
2
|
-
import { ZyfaiSDK } from "@zyfai/sdk";
|
|
3
|
-
import { createPublicClient, http } from "viem";
|
|
2
|
+
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
3
|
+
import { createPublicClient, http, parseAbi } from "viem";
|
|
4
4
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
5
5
|
|
|
6
6
|
// src/types/config.ts
|
|
@@ -792,6 +792,9 @@ function debugLog(scope, message, data) {
|
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
// src/agents/zyfai/zyfai.agent.ts
|
|
795
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
|
|
796
|
+
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
797
|
+
]);
|
|
795
798
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
796
799
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
797
800
|
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
@@ -1109,7 +1112,12 @@ var ZyfaiAgent = class {
|
|
|
1109
1112
|
const validChainId = isValidChainId(chainId);
|
|
1110
1113
|
await Promise.all([
|
|
1111
1114
|
this.ensureProtocolsSynced(validChainId, "USDC"),
|
|
1112
|
-
this.ensureProtocolsSynced(validChainId, "WETH")
|
|
1115
|
+
this.ensureProtocolsSynced(validChainId, "WETH"),
|
|
1116
|
+
// Retrofit a session key for old wallets that are already deployed and
|
|
1117
|
+
// funded but never got one — otherwise their balance stays idle until
|
|
1118
|
+
// they happen to deposit again. Only touches Safes that are missing the
|
|
1119
|
+
// validator, so pool-wallet users see no prompt. Non-fatal.
|
|
1120
|
+
this.ensureSessionKeyIfDeployed(validChainId)
|
|
1113
1121
|
]);
|
|
1114
1122
|
} catch (error) {
|
|
1115
1123
|
throw error;
|
|
@@ -1307,6 +1315,65 @@ var ZyfaiAgent = class {
|
|
|
1307
1315
|
return false;
|
|
1308
1316
|
}
|
|
1309
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Ensure Zyfai's automation can actually act on the user's Safe for this
|
|
1320
|
+
* chain, by making sure a session key exists.
|
|
1321
|
+
*
|
|
1322
|
+
* Old wallets (pre-0.2.44) are deployed WITHOUT the smart-sessions validator,
|
|
1323
|
+
* so Zyfai's rebalancer has no authorised path to move funds: deposits land
|
|
1324
|
+
* and then sit as raw tokens forever. Pre-deployed pool wallets ship with the
|
|
1325
|
+
* module already installed, which is why they never needed this.
|
|
1326
|
+
*
|
|
1327
|
+
* The gate is the on-chain module, not the wallet's class: it is the exact
|
|
1328
|
+
* condition that matters, it needs no guessing from API shapes, and it means
|
|
1329
|
+
* pool-wallet users are never prompted for a signature they don't need.
|
|
1330
|
+
*
|
|
1331
|
+
* Non-fatal by design. A failure here must not block a deposit — the funds
|
|
1332
|
+
* still arrive safely in the Safe, they just won't be deployed into a pool
|
|
1333
|
+
* until a session key exists.
|
|
1334
|
+
*/
|
|
1335
|
+
/**
|
|
1336
|
+
* Activation-time variant: only acts when the Safe already has code on this
|
|
1337
|
+
* chain. Activation must never deploy anything — that is deposit's job, and it
|
|
1338
|
+
* would mean surprise on-chain work just for opening the app.
|
|
1339
|
+
*/
|
|
1340
|
+
async ensureSessionKeyIfDeployed(chainId) {
|
|
1341
|
+
try {
|
|
1342
|
+
const ownerAddress = this.getAddress();
|
|
1343
|
+
const wallet = await this.sdk.getSmartWalletAddress(
|
|
1344
|
+
ownerAddress,
|
|
1345
|
+
chainId
|
|
1346
|
+
);
|
|
1347
|
+
if (!wallet.isDeployed) return;
|
|
1348
|
+
await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
console.warn(
|
|
1351
|
+
"[zyfai] session-key check at activation failed (non-fatal):",
|
|
1352
|
+
error instanceof Error ? error.message : String(error)
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
async ensureSessionKey(ownerAddress, chainId, smartWallet) {
|
|
1357
|
+
try {
|
|
1358
|
+
const installed = await this.getPublicClient(chainId).readContract({
|
|
1359
|
+
address: smartWallet,
|
|
1360
|
+
abi: ERC7579_IS_MODULE_INSTALLED_ABI,
|
|
1361
|
+
functionName: "isModuleInstalled",
|
|
1362
|
+
// moduleTypeId 1 = validator (ERC-7579)
|
|
1363
|
+
args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
|
|
1364
|
+
});
|
|
1365
|
+
if (installed) return;
|
|
1366
|
+
console.warn(
|
|
1367
|
+
`[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
|
|
1368
|
+
);
|
|
1369
|
+
await this.sdk.createSessionKey(ownerAddress, chainId);
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
console.warn(
|
|
1372
|
+
"[zyfai] createSessionKey skipped (non-fatal):",
|
|
1373
|
+
error instanceof Error ? error.message : String(error)
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1310
1377
|
/**
|
|
1311
1378
|
* Guarantee the user's Safe has code on `chainId` before anything transfers
|
|
1312
1379
|
* funds into it, and return its address.
|
|
@@ -1327,17 +1394,21 @@ var ZyfaiAgent = class {
|
|
|
1327
1394
|
*/
|
|
1328
1395
|
async ensureWalletDeployed(ownerAddress, chainId) {
|
|
1329
1396
|
const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
|
|
1330
|
-
if (wallet.isDeployed)
|
|
1397
|
+
if (wallet.isDeployed) {
|
|
1398
|
+
await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
|
|
1399
|
+
return wallet.address;
|
|
1400
|
+
}
|
|
1331
1401
|
const poolFirst = await this.isPredeployedPoolWallet(
|
|
1332
1402
|
ownerAddress,
|
|
1333
1403
|
wallet.address
|
|
1334
1404
|
);
|
|
1335
1405
|
const deployAsPool = () => this.sdk.deployOnChains([chainId]);
|
|
1336
1406
|
const deployAsLegacy = () => (
|
|
1337
|
-
// `createSessionKey:
|
|
1338
|
-
//
|
|
1339
|
-
//
|
|
1340
|
-
|
|
1407
|
+
// `createSessionKey: true` — per Zyfai, the old-wallet flow is
|
|
1408
|
+
// deploySafe(true) -> createSessionKey() -> deposit. Passing false (which
|
|
1409
|
+
// this did originally) deploys a Safe with no smart-sessions validator, so
|
|
1410
|
+
// deposits land and are never allocated into a pool.
|
|
1411
|
+
this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
|
|
1341
1412
|
);
|
|
1342
1413
|
const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
|
|
1343
1414
|
try {
|
|
@@ -1361,6 +1432,7 @@ var ZyfaiAgent = class {
|
|
|
1361
1432
|
this.id
|
|
1362
1433
|
);
|
|
1363
1434
|
}
|
|
1435
|
+
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
1364
1436
|
return redeployed.address;
|
|
1365
1437
|
}
|
|
1366
1438
|
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
@@ -1811,9 +1883,12 @@ async function postSponsorTransferAuth(input) {
|
|
|
1811
1883
|
{
|
|
1812
1884
|
statusCode: res.status,
|
|
1813
1885
|
responseBody: text.slice(0, 500),
|
|
1814
|
-
//
|
|
1815
|
-
//
|
|
1816
|
-
|
|
1886
|
+
// A 4xx is an explicit refusal before execution (DTO validation,
|
|
1887
|
+
// signature rejection, simulation) — nothing was broadcast, so a
|
|
1888
|
+
// user-paid retry cannot double-spend. 503 SPONSOR_UNAVAILABLE means
|
|
1889
|
+
// the relayer was out of gas, also pre-broadcast. Any other 5xx stays
|
|
1890
|
+
// ambiguous: the transfer may already be in flight.
|
|
1891
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
1817
1892
|
}
|
|
1818
1893
|
);
|
|
1819
1894
|
}
|
|
@@ -2085,9 +2160,9 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2085
2160
|
};
|
|
2086
2161
|
}
|
|
2087
2162
|
|
|
2088
|
-
// src/lib/sponsored-
|
|
2163
|
+
// src/lib/sponsored-permit2-deposit.ts
|
|
2089
2164
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2090
|
-
function
|
|
2165
|
+
function makeSponsoredPermit2Callback(deps) {
|
|
2091
2166
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2092
2167
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2093
2168
|
return async (smartWallet, chainId, amount) => {
|
|
@@ -2096,7 +2171,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2096
2171
|
if (!token) {
|
|
2097
2172
|
throw new OwneyError(
|
|
2098
2173
|
"CHAIN_UNSUPPORTED",
|
|
2099
|
-
`No sponsored
|
|
2174
|
+
`No sponsored ${deps.assetLabel} configured for chain ${chainId}`
|
|
2100
2175
|
);
|
|
2101
2176
|
}
|
|
2102
2177
|
const amountWei = BigInt(amount);
|
|
@@ -2108,14 +2183,14 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2108
2183
|
if (balance < amountWei) {
|
|
2109
2184
|
throw new OwneyError(
|
|
2110
2185
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2111
|
-
|
|
2186
|
+
`Insufficient ${deps.assetLabel} balance for this deposit.`,
|
|
2112
2187
|
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2113
2188
|
);
|
|
2114
2189
|
}
|
|
2115
2190
|
} catch (err) {
|
|
2116
2191
|
if (err instanceof OwneyError) throw err;
|
|
2117
2192
|
console.warn(
|
|
2118
|
-
|
|
2193
|
+
`[owney-sdk] ${deps.assetLabel} balance pre-check failed (non-fatal):`,
|
|
2119
2194
|
err instanceof Error ? err.message : String(err)
|
|
2120
2195
|
);
|
|
2121
2196
|
}
|
|
@@ -2123,7 +2198,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2123
2198
|
if (allowance < amountWei) {
|
|
2124
2199
|
throw new OwneyError(
|
|
2125
2200
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
2126
|
-
|
|
2201
|
+
`${deps.assetLabel} gasless deposits need a one-time Permit2 approval; call approvePermit2("${deps.assetLabel}") first`,
|
|
2127
2202
|
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
2128
2203
|
);
|
|
2129
2204
|
}
|
|
@@ -2228,7 +2303,7 @@ var OwneySDK = class {
|
|
|
2228
2303
|
routingApiBaseUrl;
|
|
2229
2304
|
referralSource;
|
|
2230
2305
|
cachedSponsoredCallback = null;
|
|
2231
|
-
|
|
2306
|
+
cachedPermit2Callbacks = /* @__PURE__ */ new Map();
|
|
2232
2307
|
initializingAgentsPromise = null;
|
|
2233
2308
|
constructor(config) {
|
|
2234
2309
|
this.apiKey = config.apiKey;
|
|
@@ -2256,7 +2331,7 @@ var OwneySDK = class {
|
|
|
2256
2331
|
}
|
|
2257
2332
|
this.state = { provider, walletAddress, chainId: null };
|
|
2258
2333
|
this.cachedSponsoredCallback = null;
|
|
2259
|
-
this.
|
|
2334
|
+
this.cachedPermit2Callbacks.clear();
|
|
2260
2335
|
}
|
|
2261
2336
|
/**
|
|
2262
2337
|
* Disconnect from all agents and clear connection state.
|
|
@@ -2270,7 +2345,7 @@ var OwneySDK = class {
|
|
|
2270
2345
|
this.disabledAgents.clear();
|
|
2271
2346
|
this.state = null;
|
|
2272
2347
|
this.cachedSponsoredCallback = null;
|
|
2273
|
-
this.
|
|
2348
|
+
this.cachedPermit2Callbacks.clear();
|
|
2274
2349
|
}
|
|
2275
2350
|
isAgentDisabled(agentId) {
|
|
2276
2351
|
return this.disabledAgents.has(agentId);
|
|
@@ -2347,25 +2422,23 @@ var OwneySDK = class {
|
|
|
2347
2422
|
return callback;
|
|
2348
2423
|
}
|
|
2349
2424
|
/**
|
|
2350
|
-
* Lazily builds (and caches) the
|
|
2351
|
-
*
|
|
2352
|
-
*
|
|
2353
|
-
*
|
|
2425
|
+
* Lazily builds (and caches) the Permit2 sponsored callback for one asset.
|
|
2426
|
+
* Used for WETH always, and for USDC when the connected owner is a smart
|
|
2427
|
+
* account — EIP-3009 verifies with ecrecover inside USDC and cannot accept a
|
|
2428
|
+
* contract signature, while Permit2 supports ERC-1271.
|
|
2354
2429
|
*/
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2430
|
+
getDefaultPermit2Callback(asset, onApproved) {
|
|
2431
|
+
const cached = this.cachedPermit2Callbacks.get(asset);
|
|
2432
|
+
if (!onApproved && cached) return cached;
|
|
2358
2433
|
const provider = this.requireConnectedProvider();
|
|
2359
2434
|
const owner = this.state.walletAddress;
|
|
2360
|
-
const callback =
|
|
2435
|
+
const callback = makeSponsoredPermit2Callback({
|
|
2361
2436
|
apiKey: this.apiKey,
|
|
2362
2437
|
baseUrl: this.routingApiBaseUrl,
|
|
2363
2438
|
ownerAddress: owner,
|
|
2439
|
+
assetLabel: asset,
|
|
2364
2440
|
onApproved,
|
|
2365
|
-
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
2366
|
-
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2367
|
-
// PublicClient/WalletClient param types — structurally identical at
|
|
2368
|
-
// runtime, but the two share a name TS treats as unrelated.
|
|
2441
|
+
tokenAddressByChain: asset === "USDC" ? SPONSORED_USDC_BY_CHAIN : SPONSORED_WETH_BY_CHAIN,
|
|
2369
2442
|
getPublicClient: (cid) => createPublicClient2({
|
|
2370
2443
|
chain: VIEM_CHAIN2[cid],
|
|
2371
2444
|
transport: custom(provider)
|
|
@@ -2376,9 +2449,34 @@ var OwneySDK = class {
|
|
|
2376
2449
|
transport: custom(provider)
|
|
2377
2450
|
})
|
|
2378
2451
|
});
|
|
2379
|
-
if (!onApproved) this.
|
|
2452
|
+
if (!onApproved) this.cachedPermit2Callbacks.set(asset, callback);
|
|
2380
2453
|
return callback;
|
|
2381
2454
|
}
|
|
2455
|
+
/**
|
|
2456
|
+
* True when the connected owner has bytecode — a deployed smart wallet, or an
|
|
2457
|
+
* EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
|
|
2458
|
+
* Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
|
|
2459
|
+
*
|
|
2460
|
+
* Fails OPEN: an unreadable code check returns false so a working EOA deposit
|
|
2461
|
+
* is never blocked by a flaky provider.
|
|
2462
|
+
*/
|
|
2463
|
+
async ownerHasCode() {
|
|
2464
|
+
try {
|
|
2465
|
+
const provider = this.requireConnectedProvider();
|
|
2466
|
+
const code = await provider.request({
|
|
2467
|
+
method: "eth_getCode",
|
|
2468
|
+
params: [this.state.walletAddress, "latest"]
|
|
2469
|
+
});
|
|
2470
|
+
return Boolean(code) && code !== "0x";
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
debugLog(
|
|
2473
|
+
"deposit",
|
|
2474
|
+
"owner code check failed; assuming EOA",
|
|
2475
|
+
error instanceof Error ? error.message : String(error)
|
|
2476
|
+
);
|
|
2477
|
+
return false;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2382
2480
|
getAgent(agentId) {
|
|
2383
2481
|
const agent = this.agents.get(agentId);
|
|
2384
2482
|
if (!agent) {
|
|
@@ -2574,7 +2672,11 @@ var OwneySDK = class {
|
|
|
2574
2672
|
}
|
|
2575
2673
|
const state = this.requireState();
|
|
2576
2674
|
const chainId = this.requireChainId();
|
|
2577
|
-
const
|
|
2675
|
+
const ownerIsSmartAccount = depositCallback === void 0 && asset !== "WETH" ? await this.ownerHasCode() : false;
|
|
2676
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" || ownerIsSmartAccount ? this.getDefaultPermit2Callback(
|
|
2677
|
+
asset === "WETH" ? "WETH" : "USDC",
|
|
2678
|
+
onApproved
|
|
2679
|
+
) : this.getDefaultSponsoredCallback(onApproved));
|
|
2578
2680
|
if (agentId) {
|
|
2579
2681
|
if (this.isAgentDisabled(agentId)) {
|
|
2580
2682
|
throw new OwneyError(
|
|
@@ -2678,12 +2780,12 @@ var OwneySDK = class {
|
|
|
2678
2780
|
try {
|
|
2679
2781
|
return await attempt(effectiveCallback);
|
|
2680
2782
|
} catch (error) {
|
|
2681
|
-
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2783
|
+
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
2682
2784
|
approvalAttempted = true;
|
|
2683
2785
|
console.warn(
|
|
2684
|
-
|
|
2786
|
+
`[owney-sdk] First ${asset} gasless deposit: sending one-time Permit2 approval...`
|
|
2685
2787
|
);
|
|
2686
|
-
await this.approvePermit2();
|
|
2788
|
+
await this.approvePermit2(asset);
|
|
2687
2789
|
continue;
|
|
2688
2790
|
}
|
|
2689
2791
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3197,23 +3299,32 @@ var OwneySDK = class {
|
|
|
3197
3299
|
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
3198
3300
|
}
|
|
3199
3301
|
/**
|
|
3200
|
-
* One-time, user-paid approval of Permit2 on the sponsored
|
|
3201
|
-
* the active chain. Required once per wallet per chain before
|
|
3202
|
-
*
|
|
3203
|
-
*
|
|
3204
|
-
*
|
|
3302
|
+
* One-time, user-paid approval of Permit2 on the given sponsored token for
|
|
3303
|
+
* the active chain. Required once per wallet per chain per token before a
|
|
3304
|
+
* gasless deposit of that asset over Permit2; afterwards deposit() is
|
|
3305
|
+
* signature-only for it. This call itself is a real transaction the user
|
|
3306
|
+
* pays gas for. Resolves only after the approval transaction is mined (1
|
|
3307
|
+
* confirmation), so a subsequent deposit() will see the new allowance;
|
|
3308
|
+
* throws if the transaction reverted.
|
|
3205
3309
|
* @returns the approval transaction hash.
|
|
3206
3310
|
*/
|
|
3207
3311
|
async approvePermit2(asset = "WETH") {
|
|
3208
|
-
void asset;
|
|
3209
3312
|
const state = this.requireState();
|
|
3210
3313
|
const chainId = this.requireChainId();
|
|
3211
|
-
this.
|
|
3212
|
-
|
|
3314
|
+
let guard = this.agents.get("zyfai");
|
|
3315
|
+
if (!guard) {
|
|
3316
|
+
try {
|
|
3317
|
+
guard = this.getEligibleAgents(chainId, asset)[0];
|
|
3318
|
+
} catch {
|
|
3319
|
+
guard = void 0;
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
if (guard) this.validateAssetSupport(guard, chainId, asset);
|
|
3323
|
+
const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
|
|
3213
3324
|
if (!token) {
|
|
3214
3325
|
throw new OwneyError(
|
|
3215
3326
|
"CHAIN_UNSUPPORTED",
|
|
3216
|
-
`No sponsored
|
|
3327
|
+
`No sponsored ${asset} on chain ${chainId}`
|
|
3217
3328
|
);
|
|
3218
3329
|
}
|
|
3219
3330
|
const provider = this.requireConnectedProvider();
|