@owney/sdk 0.7.21-beta.5 → 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 CHANGED
@@ -19,7 +19,7 @@ const sdk = new OwneySDK({ apiKey: "your-owney-api-key" });
19
19
  // Connect with an EIP-1193 wallet provider
20
20
  await sdk.connect(provider);
21
21
 
22
- // Activate agents on Base (fetches agent keys on first call)
22
+ // Activate agents on Base (creates and initializes provider wallets as needed)
23
23
  await sdk.activateAgent(8453);
24
24
 
25
25
  // Get balances across all agents
@@ -32,10 +32,10 @@ const balances = await sdk.getBalances();
32
32
 
33
33
  Create a new SDK instance.
34
34
 
35
- | Param | Type | Description |
36
- | --------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------- |
37
- | `config.apiKey` | `string` | Your Owney API key, used to authenticate with the routing API and fetch agent-specific keys |
38
- | `config.zyfaiRpcUrls` | `{ 8453?: string; 42161?: string }` (optional) | Per-chain RPC URL overrides used by Zyfai SDK network calls |
35
+ | Param | Type | Description |
36
+ | --------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
37
+ | `config.apiKey` | `string` | Your Owney API key, used to authenticate with the routing API and fetch agent-specific keys |
38
+ | `config.zyfaiRpcUrls` | `{ 8453?: string; 42161?: string }` (optional) | Per-chain RPC URL overrides used by Zyfai SDK network calls |
39
39
 
40
40
  ```typescript
41
41
  import { OwneySDK } from "@owney/sdk";
@@ -108,7 +108,7 @@ if (chainId) {
108
108
 
109
109
  ### `activateAgent(chainId, agentId?)`
110
110
 
111
- Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted. Smart wallets are pre-deployed by the underlying providers, so activation only performs authentication and profile syncing — no deploy or session-key signatures. Saves the chainId for use by all subsequent SDK calls.
111
+ Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted. Activation performs each provider's required setup before deposits are allowed. For Yieldseeker, this creates the user and agent when needed and calls the agent `/deploy` endpoint before any funding transfer. Saves the chainId for use by all subsequent SDK calls.
112
112
 
113
113
  | Param | Type | Description |
114
114
  | --------- | ---------------------- | ----------------------------------------------------------------------------- |
@@ -125,7 +125,7 @@ await sdk.activateAgent(42161);
125
125
 
126
126
  ### `deposit(options)`
127
127
 
128
- Deposit funds into a specific agent, or split equally across all agents if `agentId` is omitted. Validates that the asset is supported by the target agent(s) on the active chain, and per-agent minimum deposit amounts (defined per chain+asset on `agent.supportedAssets[].assets[].minDepositAmount`) are taken into account. When splitting across agents, `depositCallback` is invoked once per final valid agent with that agent's split amount and smart wallet address — expect multiple wallet prompts.
128
+ Deposit funds into a specific agent, or split equally across all agents if `agentId` is omitted. Validates that the asset is supported by the target agent(s) on the active chain, and per-agent minimum deposit amounts (defined per chain+asset on `agent.supportedAssets[].assets[].minDepositAmount`) are taken into account. Custom `depositCallback` implementations run once per final valid agent. The default Permit2 flow signs one recipient-bound batch authorization for every agent share.
129
129
 
130
130
  | Param | Type | Description |
131
131
  | ------------------------- | -------------------- | --------------------------------------------------------------- |
package/dist/index.cjs CHANGED
@@ -2235,7 +2235,12 @@ async function postSponsorBatchTransfer(input) {
2235
2235
  // src/lib/permit2.ts
2236
2236
  var import_viem3 = require("viem");
2237
2237
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2238
- var MAX_UINT256 = 2n ** 256n - 1n;
2238
+ function permit2ApprovalAmount(requiredAmount) {
2239
+ if (requiredAmount <= 0n) {
2240
+ throw new Error("Permit2 approval requires a positive deposit amount");
2241
+ }
2242
+ return requiredAmount;
2243
+ }
2239
2244
  var ERC20_ALLOWANCE_ABI = [
2240
2245
  {
2241
2246
  type: "function",
@@ -2985,7 +2990,7 @@ function depositDestination(context, movement) {
2985
2990
  if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
2986
2991
  return void 0;
2987
2992
  }
2988
- function movementEntry(movement, wallet, agent, asset, ownerAddress, destination) {
2993
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
2989
2994
  const from = movement.fromAddress.toLowerCase();
2990
2995
  const to = movement.toAddress.toLowerCase();
2991
2996
  const owner = ownerAddress.toLowerCase();
@@ -2995,7 +3000,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, destination
2995
3000
  return void 0;
2996
3001
  }
2997
3002
  let action;
2998
- if (from === owner && to === agentWallet) {
3003
+ if (to === agentWallet && !vaultAddresses.has(from)) {
2999
3004
  action = "Top up";
3000
3005
  } else if (from === agentWallet && to === owner) {
3001
3006
  action = "Withdraw";
@@ -3045,6 +3050,7 @@ function mapYieldseekerHistory(contexts, options) {
3045
3050
  context.agent,
3046
3051
  context.asset,
3047
3052
  options.ownerAddress,
3053
+ options.vaultAddresses,
3048
3054
  depositDestination(context, movement)
3049
3055
  )
3050
3056
  ),
@@ -3125,6 +3131,7 @@ var OWNEY_AGENT_NAME = "owney";
3125
3131
  var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3126
3132
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3127
3133
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3134
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3128
3135
  function generateYieldseekerUsername() {
3129
3136
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3130
3137
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -3186,6 +3193,8 @@ var YieldseekerAgent = class {
3186
3193
  agentContexts = /* @__PURE__ */ new Map();
3187
3194
  users = /* @__PURE__ */ new Map();
3188
3195
  pendingAgents = /* @__PURE__ */ new Map();
3196
+ yieldOptions = /* @__PURE__ */ new Map();
3197
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3189
3198
  constructor(owneyApiKey, options = {}) {
3190
3199
  this.api = new YieldseekerApiClient(
3191
3200
  owneyApiKey,
@@ -3252,7 +3261,6 @@ var YieldseekerAgent = class {
3252
3261
  chainId
3253
3262
  });
3254
3263
  }
3255
- await this.deployAfterFunding(state, chainId, context);
3256
3264
  } finally {
3257
3265
  await this.refreshSnapshotAfterMovement(
3258
3266
  state,
@@ -3404,9 +3412,20 @@ var YieldseekerAgent = class {
3404
3412
  historic: true,
3405
3413
  actions: true
3406
3414
  });
3415
+ const catalog = await Promise.all(
3416
+ [...new Set(contexts.map((context) => context.asset))].map(
3417
+ (contextAsset) => this.loadYieldOptions(contextAsset)
3418
+ )
3419
+ );
3420
+ const vaultAddresses = new Set(
3421
+ catalog.flat().filter(
3422
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
3423
+ ).map((yieldOption) => yieldOption.address.toLowerCase())
3424
+ );
3407
3425
  return mapYieldseekerHistory(contexts, {
3408
3426
  limit: options?.limit ?? 10,
3409
3427
  ownerAddress: state.walletAddress,
3428
+ vaultAddresses,
3410
3429
  ...options?.fromDate ? { fromDate: options.fromDate } : {},
3411
3430
  ...options?.toDate ? { toDate: options.toDate } : {}
3412
3431
  });
@@ -3425,18 +3444,32 @@ var YieldseekerAgent = class {
3425
3444
  const assets = requested ? [requested] : ["USDC", "WETH"];
3426
3445
  const values = await Promise.all(
3427
3446
  assets.map(async (asset) => {
3428
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3429
- const response = await this.api.request(
3430
- `/chains/8453/assets/${metadata.address}/yield-options`
3431
- );
3432
- if (!Array.isArray(response?.yieldOptions)) {
3433
- throw this.invalidResponse("yield options");
3434
- }
3435
- return { asset, yieldOptions: response.yieldOptions };
3447
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3436
3448
  })
3437
3449
  );
3438
3450
  return mapYieldseekerAgentApy(values, days);
3439
3451
  }
3452
+ async loadYieldOptions(asset) {
3453
+ const cached = this.yieldOptions.get(asset);
3454
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
3455
+ const pending = this.pendingYieldOptions.get(asset);
3456
+ if (pending) return pending;
3457
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3458
+ const request = this.api.request(
3459
+ `/chains/8453/assets/${metadata.address}/yield-options`
3460
+ ).then((response) => {
3461
+ if (!Array.isArray(response?.yieldOptions)) {
3462
+ throw this.invalidResponse("yield options");
3463
+ }
3464
+ this.yieldOptions.set(asset, {
3465
+ expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
3466
+ value: response.yieldOptions
3467
+ });
3468
+ return response.yieldOptions;
3469
+ }).finally(() => this.pendingYieldOptions.delete(asset));
3470
+ this.pendingYieldOptions.set(asset, request);
3471
+ return request;
3472
+ }
3440
3473
  userKey(state, chainId) {
3441
3474
  return `${state.walletAddress.toLowerCase()}:${chainId}`;
3442
3475
  }
@@ -3515,8 +3548,9 @@ var YieldseekerAgent = class {
3515
3548
  const pending = this.pendingAgents.get(key2);
3516
3549
  if (pending) return pending;
3517
3550
  const request = this.resolveAgent(state, chainId, asset, true).then(
3518
- (context) => {
3551
+ async (context) => {
3519
3552
  if (!context) throw this.invalidResponse("agent creation");
3553
+ await this.deployAgent(state, chainId, context);
3520
3554
  this.agentContexts.set(key2, context);
3521
3555
  return context;
3522
3556
  }
@@ -3661,24 +3695,21 @@ var YieldseekerAgent = class {
3661
3695
  ...actions?.actions ? { actions: actions.actions } : {}
3662
3696
  };
3663
3697
  }
3664
- async deployAfterFunding(state, chainId, context) {
3698
+ async deployAgent(state, chainId, context) {
3665
3699
  if (context.wallet.initializedDate != null) return;
3666
- try {
3667
- const deployed = await this.walletRequest(
3668
- state,
3669
- chainId,
3670
- this.agentPath(context, "deploy"),
3671
- { method: "POST", body: {} }
3672
- );
3673
- if (deployed?.agentWallet) {
3674
- context.wallet = deployed.agentWallet;
3675
- }
3676
- } catch (error) {
3677
- console.warn(
3678
- "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3679
- error
3680
- );
3700
+ const walletAddress = context.wallet.walletAddress.toLowerCase();
3701
+ const deployed = await this.walletRequest(
3702
+ state,
3703
+ chainId,
3704
+ this.agentPath(context, "deploy"),
3705
+ { method: "POST", body: {} }
3706
+ );
3707
+ if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3708
+ throw this.invalidResponse("agent deployment", {
3709
+ reason: "Deploy did not return the expected Agent Wallet."
3710
+ });
3681
3711
  }
3712
+ context.wallet = deployed.agentWallet;
3682
3713
  }
3683
3714
  async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3684
3715
  try {
@@ -5246,7 +5277,14 @@ var OwneySDK = class {
5246
5277
  } catch (error) {
5247
5278
  if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5248
5279
  throw error;
5249
- await this.approvePermit2(asset);
5280
+ const requiredAmount = transfers.reduce(
5281
+ (sum, transfer) => sum + BigInt(transfer.amount),
5282
+ 0n
5283
+ );
5284
+ await this.approvePermit2(
5285
+ asset,
5286
+ requiredAmount
5287
+ );
5250
5288
  return batchTransfer(cid, transfers);
5251
5289
  }
5252
5290
  }
@@ -5333,7 +5371,10 @@ var OwneySDK = class {
5333
5371
  console.warn(
5334
5372
  "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5335
5373
  );
5336
- await this.approvePermit2(asset);
5374
+ await this.approvePermit2(
5375
+ asset,
5376
+ BigInt(amount)
5377
+ );
5337
5378
  continue;
5338
5379
  }
5339
5380
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -5986,14 +6027,15 @@ var OwneySDK = class {
5986
6027
  return pending;
5987
6028
  }
5988
6029
  /**
5989
- * One-time, user-paid approval of Permit2 on the selected token for
5990
- * the active chain. Required once per wallet, token and chain before sponsored
5991
- * deposits; afterwards deposit() is signature-only. Resolves only after the
5992
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
5993
- * will see the new allowance; throws if the transaction reverted.
6030
+ * User-paid approval of Permit2 on the selected token for the active chain.
6031
+ * Approves exactly the pending deposit amount. Another approval is required
6032
+ * for a later deposit once this allowance has been consumed. Resolves after
6033
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
6034
+ *
6035
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
5994
6036
  * @returns the approval transaction hash.
5995
6037
  */
5996
- async approvePermit2(asset = "WETH") {
6038
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
5997
6039
  const state = this.requireState();
5998
6040
  const chainId = this.requireChainId();
5999
6041
  this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
@@ -6005,6 +6047,11 @@ var OwneySDK = class {
6005
6047
  );
6006
6048
  }
6007
6049
  const provider = this.requireConnectedProvider();
6050
+ const publicClient = (0, import_viem11.createPublicClient)({
6051
+ chain: VIEM_CHAIN2[chainId],
6052
+ transport: (0, import_viem11.custom)(provider)
6053
+ });
6054
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
6008
6055
  const wallet = (0, import_viem11.createWalletClient)({
6009
6056
  account: state.walletAddress,
6010
6057
  chain: VIEM_CHAIN2[chainId],
@@ -6014,14 +6061,10 @@ var OwneySDK = class {
6014
6061
  address: token,
6015
6062
  abi: ERC20_ALLOWANCE_ABI,
6016
6063
  functionName: "approve",
6017
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6064
+ args: [PERMIT2_ADDRESS, approvalAmount],
6018
6065
  account: state.walletAddress,
6019
6066
  chain: VIEM_CHAIN2[chainId]
6020
6067
  });
6021
- const publicClient = (0, import_viem11.createPublicClient)({
6022
- chain: VIEM_CHAIN2[chainId],
6023
- transport: (0, import_viem11.custom)(provider)
6024
- });
6025
6068
  const receipt = await publicClient.waitForTransactionReceipt({
6026
6069
  hash,
6027
6070
  confirmations: 1
package/dist/index.d.cts CHANGED
@@ -719,14 +719,15 @@ declare class OwneySDK {
719
719
  */
720
720
  ensureAutoSelectProtocols(asset: "USDC" | "WETH", agentId?: AgentId): Promise<boolean>;
721
721
  /**
722
- * One-time, user-paid approval of Permit2 on the selected token for
723
- * the active chain. Required once per wallet, token and chain before sponsored
724
- * deposits; afterwards deposit() is signature-only. Resolves only after the
725
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
726
- * will see the new allowance; throws if the transaction reverted.
722
+ * User-paid approval of Permit2 on the selected token for the active chain.
723
+ * Approves exactly the pending deposit amount. Another approval is required
724
+ * for a later deposit once this allowance has been consumed. Resolves after
725
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
726
+ *
727
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
727
728
  * @returns the approval transaction hash.
728
729
  */
729
- approvePermit2(asset?: OwneySupportedTokens): Promise<`0x${string}`>;
730
+ approvePermit2(asset?: OwneySupportedTokens, requiredAmount?: bigint): Promise<`0x${string}`>;
730
731
  /**
731
732
  * Get the agent's average APY performance over a time period. Does not require a wallet connection.
732
733
  * @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
@@ -797,6 +798,8 @@ declare class YieldseekerAgent implements IAgent {
797
798
  private readonly agentContexts;
798
799
  private readonly users;
799
800
  private readonly pendingAgents;
801
+ private readonly yieldOptions;
802
+ private readonly pendingYieldOptions;
800
803
  constructor(owneyApiKey: string, options?: YieldseekerAgentOptions);
801
804
  disconnect(): Promise<void>;
802
805
  activateAgent(state: ConnectionState, chainId: number, asset?: OwneySupportedTokens): Promise<void>;
@@ -808,6 +811,7 @@ declare class YieldseekerAgent implements IAgent {
808
811
  getHistory(state: ConnectionState, chainId: number, options?: HistoryFilters): Promise<OwneyAgentHistory>;
809
812
  getUserProfile(state: ConnectionState, chainId: number): Promise<AgentUserProfile>;
810
813
  getAgentApy(days: DailyApyDays, options?: AgentApyOptions): Promise<AgentApy>;
814
+ private loadYieldOptions;
811
815
  private userKey;
812
816
  private contextKey;
813
817
  private resolveUser;
@@ -817,7 +821,7 @@ declare class YieldseekerAgent implements IAgent {
817
821
  private resolveAgent;
818
822
  private loadPortfolio;
819
823
  private loadPortfolioContext;
820
- private deployAfterFunding;
824
+ private deployAgent;
821
825
  private refreshSnapshotAfterMovement;
822
826
  private agentPath;
823
827
  private walletRequest;
package/dist/index.d.ts CHANGED
@@ -719,14 +719,15 @@ declare class OwneySDK {
719
719
  */
720
720
  ensureAutoSelectProtocols(asset: "USDC" | "WETH", agentId?: AgentId): Promise<boolean>;
721
721
  /**
722
- * One-time, user-paid approval of Permit2 on the selected token for
723
- * the active chain. Required once per wallet, token and chain before sponsored
724
- * deposits; afterwards deposit() is signature-only. Resolves only after the
725
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
726
- * will see the new allowance; throws if the transaction reverted.
722
+ * User-paid approval of Permit2 on the selected token for the active chain.
723
+ * Approves exactly the pending deposit amount. Another approval is required
724
+ * for a later deposit once this allowance has been consumed. Resolves after
725
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
726
+ *
727
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
727
728
  * @returns the approval transaction hash.
728
729
  */
729
- approvePermit2(asset?: OwneySupportedTokens): Promise<`0x${string}`>;
730
+ approvePermit2(asset?: OwneySupportedTokens, requiredAmount?: bigint): Promise<`0x${string}`>;
730
731
  /**
731
732
  * Get the agent's average APY performance over a time period. Does not require a wallet connection.
732
733
  * @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
@@ -797,6 +798,8 @@ declare class YieldseekerAgent implements IAgent {
797
798
  private readonly agentContexts;
798
799
  private readonly users;
799
800
  private readonly pendingAgents;
801
+ private readonly yieldOptions;
802
+ private readonly pendingYieldOptions;
800
803
  constructor(owneyApiKey: string, options?: YieldseekerAgentOptions);
801
804
  disconnect(): Promise<void>;
802
805
  activateAgent(state: ConnectionState, chainId: number, asset?: OwneySupportedTokens): Promise<void>;
@@ -808,6 +811,7 @@ declare class YieldseekerAgent implements IAgent {
808
811
  getHistory(state: ConnectionState, chainId: number, options?: HistoryFilters): Promise<OwneyAgentHistory>;
809
812
  getUserProfile(state: ConnectionState, chainId: number): Promise<AgentUserProfile>;
810
813
  getAgentApy(days: DailyApyDays, options?: AgentApyOptions): Promise<AgentApy>;
814
+ private loadYieldOptions;
811
815
  private userKey;
812
816
  private contextKey;
813
817
  private resolveUser;
@@ -817,7 +821,7 @@ declare class YieldseekerAgent implements IAgent {
817
821
  private resolveAgent;
818
822
  private loadPortfolio;
819
823
  private loadPortfolioContext;
820
- private deployAfterFunding;
824
+ private deployAgent;
821
825
  private refreshSnapshotAfterMovement;
822
826
  private agentPath;
823
827
  private walletRequest;
package/dist/index.js CHANGED
@@ -2209,7 +2209,12 @@ async function postSponsorBatchTransfer(input) {
2209
2209
  // src/lib/permit2.ts
2210
2210
  import { bytesToHex as bytesToHex2 } from "viem";
2211
2211
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2212
- var MAX_UINT256 = 2n ** 256n - 1n;
2212
+ function permit2ApprovalAmount(requiredAmount) {
2213
+ if (requiredAmount <= 0n) {
2214
+ throw new Error("Permit2 approval requires a positive deposit amount");
2215
+ }
2216
+ return requiredAmount;
2217
+ }
2213
2218
  var ERC20_ALLOWANCE_ABI = [
2214
2219
  {
2215
2220
  type: "function",
@@ -2964,7 +2969,7 @@ function depositDestination(context, movement) {
2964
2969
  if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
2965
2970
  return void 0;
2966
2971
  }
2967
- function movementEntry(movement, wallet, agent, asset, ownerAddress, destination) {
2972
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
2968
2973
  const from = movement.fromAddress.toLowerCase();
2969
2974
  const to = movement.toAddress.toLowerCase();
2970
2975
  const owner = ownerAddress.toLowerCase();
@@ -2974,7 +2979,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, destination
2974
2979
  return void 0;
2975
2980
  }
2976
2981
  let action;
2977
- if (from === owner && to === agentWallet) {
2982
+ if (to === agentWallet && !vaultAddresses.has(from)) {
2978
2983
  action = "Top up";
2979
2984
  } else if (from === agentWallet && to === owner) {
2980
2985
  action = "Withdraw";
@@ -3024,6 +3029,7 @@ function mapYieldseekerHistory(contexts, options) {
3024
3029
  context.agent,
3025
3030
  context.asset,
3026
3031
  options.ownerAddress,
3032
+ options.vaultAddresses,
3027
3033
  depositDestination(context, movement)
3028
3034
  )
3029
3035
  ),
@@ -3104,6 +3110,7 @@ var OWNEY_AGENT_NAME = "owney";
3104
3110
  var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3105
3111
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3106
3112
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3113
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3107
3114
  function generateYieldseekerUsername() {
3108
3115
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3109
3116
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -3165,6 +3172,8 @@ var YieldseekerAgent = class {
3165
3172
  agentContexts = /* @__PURE__ */ new Map();
3166
3173
  users = /* @__PURE__ */ new Map();
3167
3174
  pendingAgents = /* @__PURE__ */ new Map();
3175
+ yieldOptions = /* @__PURE__ */ new Map();
3176
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3168
3177
  constructor(owneyApiKey, options = {}) {
3169
3178
  this.api = new YieldseekerApiClient(
3170
3179
  owneyApiKey,
@@ -3231,7 +3240,6 @@ var YieldseekerAgent = class {
3231
3240
  chainId
3232
3241
  });
3233
3242
  }
3234
- await this.deployAfterFunding(state, chainId, context);
3235
3243
  } finally {
3236
3244
  await this.refreshSnapshotAfterMovement(
3237
3245
  state,
@@ -3383,9 +3391,20 @@ var YieldseekerAgent = class {
3383
3391
  historic: true,
3384
3392
  actions: true
3385
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
+ );
3386
3404
  return mapYieldseekerHistory(contexts, {
3387
3405
  limit: options?.limit ?? 10,
3388
3406
  ownerAddress: state.walletAddress,
3407
+ vaultAddresses,
3389
3408
  ...options?.fromDate ? { fromDate: options.fromDate } : {},
3390
3409
  ...options?.toDate ? { toDate: options.toDate } : {}
3391
3410
  });
@@ -3404,18 +3423,32 @@ var YieldseekerAgent = class {
3404
3423
  const assets = requested ? [requested] : ["USDC", "WETH"];
3405
3424
  const values = await Promise.all(
3406
3425
  assets.map(async (asset) => {
3407
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3408
- const response = await this.api.request(
3409
- `/chains/8453/assets/${metadata.address}/yield-options`
3410
- );
3411
- if (!Array.isArray(response?.yieldOptions)) {
3412
- throw this.invalidResponse("yield options");
3413
- }
3414
- return { asset, yieldOptions: response.yieldOptions };
3426
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3415
3427
  })
3416
3428
  );
3417
3429
  return mapYieldseekerAgentApy(values, days);
3418
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
+ }
3419
3452
  userKey(state, chainId) {
3420
3453
  return `${state.walletAddress.toLowerCase()}:${chainId}`;
3421
3454
  }
@@ -3494,8 +3527,9 @@ var YieldseekerAgent = class {
3494
3527
  const pending = this.pendingAgents.get(key2);
3495
3528
  if (pending) return pending;
3496
3529
  const request = this.resolveAgent(state, chainId, asset, true).then(
3497
- (context) => {
3530
+ async (context) => {
3498
3531
  if (!context) throw this.invalidResponse("agent creation");
3532
+ await this.deployAgent(state, chainId, context);
3499
3533
  this.agentContexts.set(key2, context);
3500
3534
  return context;
3501
3535
  }
@@ -3640,24 +3674,21 @@ var YieldseekerAgent = class {
3640
3674
  ...actions?.actions ? { actions: actions.actions } : {}
3641
3675
  };
3642
3676
  }
3643
- async deployAfterFunding(state, chainId, context) {
3677
+ async deployAgent(state, chainId, context) {
3644
3678
  if (context.wallet.initializedDate != null) return;
3645
- try {
3646
- const deployed = await this.walletRequest(
3647
- state,
3648
- chainId,
3649
- this.agentPath(context, "deploy"),
3650
- { method: "POST", body: {} }
3651
- );
3652
- if (deployed?.agentWallet) {
3653
- context.wallet = deployed.agentWallet;
3654
- }
3655
- } catch (error) {
3656
- console.warn(
3657
- "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3658
- error
3659
- );
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
+ });
3660
3690
  }
3691
+ context.wallet = deployed.agentWallet;
3661
3692
  }
3662
3693
  async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3663
3694
  try {
@@ -5233,7 +5264,14 @@ var OwneySDK = class {
5233
5264
  } catch (error) {
5234
5265
  if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5235
5266
  throw error;
5236
- await this.approvePermit2(asset);
5267
+ const requiredAmount = transfers.reduce(
5268
+ (sum, transfer) => sum + BigInt(transfer.amount),
5269
+ 0n
5270
+ );
5271
+ await this.approvePermit2(
5272
+ asset,
5273
+ requiredAmount
5274
+ );
5237
5275
  return batchTransfer(cid, transfers);
5238
5276
  }
5239
5277
  }
@@ -5320,7 +5358,10 @@ var OwneySDK = class {
5320
5358
  console.warn(
5321
5359
  "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5322
5360
  );
5323
- await this.approvePermit2(asset);
5361
+ await this.approvePermit2(
5362
+ asset,
5363
+ BigInt(amount)
5364
+ );
5324
5365
  continue;
5325
5366
  }
5326
5367
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -5973,14 +6014,15 @@ var OwneySDK = class {
5973
6014
  return pending;
5974
6015
  }
5975
6016
  /**
5976
- * One-time, user-paid approval of Permit2 on the selected token for
5977
- * the active chain. Required once per wallet, token and chain before sponsored
5978
- * deposits; afterwards deposit() is signature-only. Resolves only after the
5979
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
5980
- * will see the new allowance; throws if the transaction reverted.
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.
5981
6023
  * @returns the approval transaction hash.
5982
6024
  */
5983
- async approvePermit2(asset = "WETH") {
6025
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
5984
6026
  const state = this.requireState();
5985
6027
  const chainId = this.requireChainId();
5986
6028
  this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
@@ -5992,6 +6034,11 @@ var OwneySDK = class {
5992
6034
  );
5993
6035
  }
5994
6036
  const provider = this.requireConnectedProvider();
6037
+ const publicClient = createPublicClient4({
6038
+ chain: VIEM_CHAIN2[chainId],
6039
+ transport: custom3(provider)
6040
+ });
6041
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
5995
6042
  const wallet = createWalletClient3({
5996
6043
  account: state.walletAddress,
5997
6044
  chain: VIEM_CHAIN2[chainId],
@@ -6001,14 +6048,10 @@ var OwneySDK = class {
6001
6048
  address: token,
6002
6049
  abi: ERC20_ALLOWANCE_ABI,
6003
6050
  functionName: "approve",
6004
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6051
+ args: [PERMIT2_ADDRESS, approvalAmount],
6005
6052
  account: state.walletAddress,
6006
6053
  chain: VIEM_CHAIN2[chainId]
6007
6054
  });
6008
- const publicClient = createPublicClient4({
6009
- chain: VIEM_CHAIN2[chainId],
6010
- transport: custom3(provider)
6011
- });
6012
6055
  const receipt = await publicClient.waitForTransactionReceipt({
6013
6056
  hash,
6014
6057
  confirmations: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.21-beta.5",
3
+ "version": "0.7.21-beta.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",