@owney/sdk 0.7.4 → 0.7.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/dist/index.cjs +124 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +126 -9
- 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) {
|
|
@@ -2001,6 +2073,41 @@ async function readErc20Balance(publicClient, token, owner) {
|
|
|
2001
2073
|
});
|
|
2002
2074
|
}
|
|
2003
2075
|
|
|
2076
|
+
// src/lib/chain-guard.ts
|
|
2077
|
+
var CHAIN_NAMES = {
|
|
2078
|
+
1: "Ethereum",
|
|
2079
|
+
8453: "Base",
|
|
2080
|
+
42161: "Arbitrum"
|
|
2081
|
+
};
|
|
2082
|
+
function chainName(chainId) {
|
|
2083
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2084
|
+
}
|
|
2085
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2086
|
+
const actual = await pub.getChainId();
|
|
2087
|
+
if (actual === expected) return;
|
|
2088
|
+
try {
|
|
2089
|
+
await wallet.switchChain({ id: expected });
|
|
2090
|
+
} catch (error) {
|
|
2091
|
+
throw new OwneyError(
|
|
2092
|
+
"CHAIN_MISMATCH",
|
|
2093
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2094
|
+
{
|
|
2095
|
+
expectedChainId: expected,
|
|
2096
|
+
actualChainId: actual,
|
|
2097
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2098
|
+
}
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
const after = await pub.getChainId();
|
|
2102
|
+
if (after !== expected) {
|
|
2103
|
+
throw new OwneyError(
|
|
2104
|
+
"CHAIN_MISMATCH",
|
|
2105
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2106
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2004
2111
|
// src/lib/sponsored-deposit.ts
|
|
2005
2112
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2006
2113
|
function makeSponsoredDepositCallback(deps) {
|
|
@@ -2016,6 +2123,7 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2016
2123
|
}
|
|
2017
2124
|
const pub = deps.getPublicClient(cid);
|
|
2018
2125
|
const wallet = deps.getWalletClient(cid);
|
|
2126
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2019
2127
|
try {
|
|
2020
2128
|
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2021
2129
|
if (balance < BigInt(amount)) {
|
|
@@ -2027,6 +2135,10 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
2027
2135
|
}
|
|
2028
2136
|
} catch (err) {
|
|
2029
2137
|
if (err instanceof OwneyError) throw err;
|
|
2138
|
+
console.warn(
|
|
2139
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2140
|
+
err instanceof Error ? err.message : String(err)
|
|
2141
|
+
);
|
|
2030
2142
|
}
|
|
2031
2143
|
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2032
2144
|
const validAfter = 0n;
|
|
@@ -2090,6 +2202,8 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2090
2202
|
}
|
|
2091
2203
|
const amountWei = BigInt(amount);
|
|
2092
2204
|
const pub = deps.getPublicClient(cid);
|
|
2205
|
+
const wallet = deps.getWalletClient(cid);
|
|
2206
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2093
2207
|
try {
|
|
2094
2208
|
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2095
2209
|
if (balance < amountWei) {
|
|
@@ -2101,6 +2215,10 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2101
2215
|
}
|
|
2102
2216
|
} catch (err) {
|
|
2103
2217
|
if (err instanceof OwneyError) throw err;
|
|
2218
|
+
console.warn(
|
|
2219
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2220
|
+
err instanceof Error ? err.message : String(err)
|
|
2221
|
+
);
|
|
2104
2222
|
}
|
|
2105
2223
|
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
2106
2224
|
if (allowance < amountWei) {
|
|
@@ -2128,7 +2246,6 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2128
2246
|
deadline
|
|
2129
2247
|
}
|
|
2130
2248
|
});
|
|
2131
|
-
const wallet = deps.getWalletClient(cid);
|
|
2132
2249
|
const signature = await wallet.signTypedData({
|
|
2133
2250
|
account: deps.ownerAddress,
|
|
2134
2251
|
...typedData
|
package/dist/index.d.cts
CHANGED
|
@@ -609,7 +609,7 @@ declare class OwneySDK {
|
|
|
609
609
|
getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
-
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
612
|
+
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
613
613
|
declare class OwneyError extends Error {
|
|
614
614
|
readonly code: OwneyErrorCode;
|
|
615
615
|
readonly details?: Record<string, unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -609,7 +609,7 @@ declare class OwneySDK {
|
|
|
609
609
|
getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
-
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
612
|
+
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
613
613
|
declare class OwneyError extends Error {
|
|
614
614
|
readonly code: OwneyErrorCode;
|
|
615
615
|
readonly details?: Record<string, unknown>;
|
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) {
|
|
@@ -1972,6 +2044,41 @@ async function readErc20Balance(publicClient, token, owner) {
|
|
|
1972
2044
|
});
|
|
1973
2045
|
}
|
|
1974
2046
|
|
|
2047
|
+
// src/lib/chain-guard.ts
|
|
2048
|
+
var CHAIN_NAMES = {
|
|
2049
|
+
1: "Ethereum",
|
|
2050
|
+
8453: "Base",
|
|
2051
|
+
42161: "Arbitrum"
|
|
2052
|
+
};
|
|
2053
|
+
function chainName(chainId) {
|
|
2054
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2055
|
+
}
|
|
2056
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2057
|
+
const actual = await pub.getChainId();
|
|
2058
|
+
if (actual === expected) return;
|
|
2059
|
+
try {
|
|
2060
|
+
await wallet.switchChain({ id: expected });
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
throw new OwneyError(
|
|
2063
|
+
"CHAIN_MISMATCH",
|
|
2064
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2065
|
+
{
|
|
2066
|
+
expectedChainId: expected,
|
|
2067
|
+
actualChainId: actual,
|
|
2068
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2069
|
+
}
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
const after = await pub.getChainId();
|
|
2073
|
+
if (after !== expected) {
|
|
2074
|
+
throw new OwneyError(
|
|
2075
|
+
"CHAIN_MISMATCH",
|
|
2076
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2077
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1975
2082
|
// src/lib/sponsored-deposit.ts
|
|
1976
2083
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
1977
2084
|
function makeSponsoredDepositCallback(deps) {
|
|
@@ -1987,6 +2094,7 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
1987
2094
|
}
|
|
1988
2095
|
const pub = deps.getPublicClient(cid);
|
|
1989
2096
|
const wallet = deps.getWalletClient(cid);
|
|
2097
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
1990
2098
|
try {
|
|
1991
2099
|
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
1992
2100
|
if (balance < BigInt(amount)) {
|
|
@@ -1998,6 +2106,10 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
1998
2106
|
}
|
|
1999
2107
|
} catch (err) {
|
|
2000
2108
|
if (err instanceof OwneyError) throw err;
|
|
2109
|
+
console.warn(
|
|
2110
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2111
|
+
err instanceof Error ? err.message : String(err)
|
|
2112
|
+
);
|
|
2001
2113
|
}
|
|
2002
2114
|
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2003
2115
|
const validAfter = 0n;
|
|
@@ -2061,6 +2173,8 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2061
2173
|
}
|
|
2062
2174
|
const amountWei = BigInt(amount);
|
|
2063
2175
|
const pub = deps.getPublicClient(cid);
|
|
2176
|
+
const wallet = deps.getWalletClient(cid);
|
|
2177
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2064
2178
|
try {
|
|
2065
2179
|
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2066
2180
|
if (balance < amountWei) {
|
|
@@ -2072,6 +2186,10 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2072
2186
|
}
|
|
2073
2187
|
} catch (err) {
|
|
2074
2188
|
if (err instanceof OwneyError) throw err;
|
|
2189
|
+
console.warn(
|
|
2190
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2191
|
+
err instanceof Error ? err.message : String(err)
|
|
2192
|
+
);
|
|
2075
2193
|
}
|
|
2076
2194
|
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
2077
2195
|
if (allowance < amountWei) {
|
|
@@ -2099,7 +2217,6 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2099
2217
|
deadline
|
|
2100
2218
|
}
|
|
2101
2219
|
});
|
|
2102
|
-
const wallet = deps.getWalletClient(cid);
|
|
2103
2220
|
const signature = await wallet.signTypedData({
|
|
2104
2221
|
account: deps.ownerAddress,
|
|
2105
2222
|
...typedData
|