@owney/sdk 0.2.9 → 0.2.10
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 +19 -1
- package/dist/index.cjs +216 -44
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +216 -44
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -167,6 +167,14 @@ console.log(results.agentResults.sail.amount); // "50000000"
|
|
|
167
167
|
|
|
168
168
|
Withdraw funds from a specific agent, or all agents that support the active chain+asset if `agentId` is omitted. Validates that the asset is supported.
|
|
169
169
|
|
|
170
|
+
When `agentId` is omitted with a partial `amount`, the SDK splits the requested amount proportionally across eligible agents based on each agent's per-asset balance. If an agent's withdraw fails, its share is redistributed to remaining not-yet-attempted agents (capped by their headroom). Throws:
|
|
171
|
+
|
|
172
|
+
- `WITHDRAW_INSUFFICIENT_BALANCE` — total available across agents is less than `amount`.
|
|
173
|
+
- `WITHDRAW_ALL_FAILED` — every agent's withdraw call failed.
|
|
174
|
+
- `WITHDRAW_PARTIAL_FAILURE` — some agents succeeded but total withdrawn is less than `amount`. Successful txs are surfaced via `error.details.partialResults`; per-agent failure messages via `error.details.agentErrors`.
|
|
175
|
+
|
|
176
|
+
`WITHDRAW_ALL_FAILED` also carries per-agent failure messages via `error.details.agentErrors` (a `Record<agentId, string>`). Any error originating from a specific agent also has `error.agentId` set and a `[agent:<id>]` prefix on `error.message`.
|
|
177
|
+
|
|
170
178
|
| Param | Type | Description |
|
|
171
179
|
| ----------------- | -------------------- | --------------------------------------------- |
|
|
172
180
|
| `options.asset` | `string` | Asset symbol (e.g. `"USDC"`) |
|
|
@@ -185,6 +193,12 @@ const result = await sdk.withdraw({
|
|
|
185
193
|
console.log(result.type); // "partial"
|
|
186
194
|
console.log(result.amount); // "50000000"
|
|
187
195
|
|
|
196
|
+
// Partial withdrawal split proportionally across all agents
|
|
197
|
+
// e.g. zyfai=$5, sail=$2, amount=$4 → zyfai withdraws $3, sail withdraws $1
|
|
198
|
+
const split = await sdk.withdraw({ asset: "USDC", amount: "4000000" });
|
|
199
|
+
console.log(split.agentResult.zyfai.amount); // "3000000"
|
|
200
|
+
console.log(split.agentResult.sail.amount); // "1000000"
|
|
201
|
+
|
|
188
202
|
// Full withdrawal from all agents
|
|
189
203
|
const results = await sdk.withdraw({ asset: "USDC" });
|
|
190
204
|
console.log(results.agentResult.zyfai.type); // "full"
|
|
@@ -630,7 +644,7 @@ type DepositCallback = (
|
|
|
630
644
|
|
|
631
645
|
### Error Handling
|
|
632
646
|
|
|
633
|
-
All errors thrown by the SDK are instances of `OwneyError` (which extends `Error`), each carrying a stable `code` string
|
|
647
|
+
All errors thrown by the SDK are instances of `OwneyError` (which extends `Error`), each carrying a stable `code` string, optional `details` object, and optional `agentId` identifying the originating agent. When `agentId` is set, `error.message` is also prefixed with `[agent:<id>]` for logs.
|
|
634
648
|
|
|
635
649
|
```typescript
|
|
636
650
|
import { OwneyError } from "@owney/sdk";
|
|
@@ -680,6 +694,9 @@ type OwneyErrorCode =
|
|
|
680
694
|
| "DEPOSIT_NO_PERMITTED_TOKENS" // No permitted tokens available for deposit
|
|
681
695
|
// Withdraw
|
|
682
696
|
| "WITHDRAW_NO_PERMITTED_TOKENS" // No permitted tokens available for withdrawal
|
|
697
|
+
| "WITHDRAW_INSUFFICIENT_BALANCE" // Requested amount exceeds total available across agents
|
|
698
|
+
| "WITHDRAW_ALL_FAILED" // Every eligible agent's withdraw call failed
|
|
699
|
+
| "WITHDRAW_PARTIAL_FAILURE" // Some agents succeeded; total withdrawn < requested
|
|
683
700
|
// API
|
|
684
701
|
| "API_ROUTING_ERROR" // Routing API returned a non-OK HTTP status
|
|
685
702
|
| "API_ROUTING_FAILED" // Routing API returned { success: false }
|
|
@@ -695,6 +712,7 @@ type OwneyErrorCode =
|
|
|
695
712
|
class OwneyError extends Error {
|
|
696
713
|
readonly code: OwneyErrorCode;
|
|
697
714
|
readonly details?: Record<string, unknown>;
|
|
715
|
+
readonly agentId?: string;
|
|
698
716
|
}
|
|
699
717
|
```
|
|
700
718
|
|
package/dist/index.cjs
CHANGED
|
@@ -42,17 +42,20 @@ var SupportedAssets = [
|
|
|
42
42
|
{
|
|
43
43
|
address: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
|
|
44
44
|
symbol: "USDC",
|
|
45
|
-
chainId: 8453
|
|
45
|
+
chainId: 8453,
|
|
46
|
+
decimals: 6
|
|
46
47
|
},
|
|
47
48
|
{
|
|
48
49
|
address: `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`,
|
|
49
50
|
symbol: "USDC",
|
|
50
|
-
chainId: 42161
|
|
51
|
+
chainId: 42161,
|
|
52
|
+
decimals: 6
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
address: `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9`,
|
|
54
56
|
symbol: "USDT",
|
|
55
|
-
chainId: 42161
|
|
57
|
+
chainId: 42161,
|
|
58
|
+
decimals: 6
|
|
56
59
|
}
|
|
57
60
|
];
|
|
58
61
|
|
|
@@ -60,11 +63,13 @@ var SupportedAssets = [
|
|
|
60
63
|
var OwneyError = class extends Error {
|
|
61
64
|
code;
|
|
62
65
|
details;
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
agentId;
|
|
67
|
+
constructor(code, message, details, agentId) {
|
|
68
|
+
super(agentId ? `[agent:${agentId}] ${message}` : message);
|
|
65
69
|
this.name = "OwneyError";
|
|
66
70
|
this.code = code;
|
|
67
71
|
this.details = details;
|
|
72
|
+
this.agentId = agentId;
|
|
68
73
|
}
|
|
69
74
|
};
|
|
70
75
|
var AgentNotFoundError = class extends OwneyError {
|
|
@@ -72,7 +77,8 @@ var AgentNotFoundError = class extends OwneyError {
|
|
|
72
77
|
super(
|
|
73
78
|
"AGENT_NOT_FOUND",
|
|
74
79
|
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
75
|
-
{ agentId, available }
|
|
80
|
+
{ agentId, available },
|
|
81
|
+
agentId
|
|
76
82
|
);
|
|
77
83
|
this.name = "AgentNotFoundError";
|
|
78
84
|
}
|
|
@@ -339,7 +345,9 @@ var ZyfaiAgent = class {
|
|
|
339
345
|
if (!this.connectedAddress) {
|
|
340
346
|
throw new OwneyError(
|
|
341
347
|
"NOT_CONNECTED",
|
|
342
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
348
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
349
|
+
void 0,
|
|
350
|
+
this.id
|
|
343
351
|
);
|
|
344
352
|
}
|
|
345
353
|
return this.connectedAddress;
|
|
@@ -445,13 +453,14 @@ var ZyfaiAgent = class {
|
|
|
445
453
|
}
|
|
446
454
|
}
|
|
447
455
|
// --- IAgent: Fund operations ---
|
|
448
|
-
async withdraw(state, chainId, amount) {
|
|
456
|
+
async withdraw(state, chainId, token, amount) {
|
|
449
457
|
await this.connectFull(state, chainId);
|
|
450
458
|
const validChainId = isValidChainId(chainId);
|
|
451
459
|
const raw = await this.sdk.withdrawFunds(
|
|
452
460
|
this.getAddress(),
|
|
453
461
|
validChainId,
|
|
454
|
-
amount
|
|
462
|
+
amount,
|
|
463
|
+
token
|
|
455
464
|
);
|
|
456
465
|
return mapWithdraw(raw);
|
|
457
466
|
}
|
|
@@ -709,6 +718,7 @@ function remapApyPerAssetToSymbols(raw) {
|
|
|
709
718
|
}
|
|
710
719
|
|
|
711
720
|
// src/agents/sail/sail.agent.ts
|
|
721
|
+
var import_viem = require("viem");
|
|
712
722
|
var SAIL_API_BASE = "https://app.sail.money";
|
|
713
723
|
var PROJECT_ID = "sail";
|
|
714
724
|
var PAGE_ID = "home";
|
|
@@ -766,7 +776,8 @@ var SailAgent = class {
|
|
|
766
776
|
throw new OwneyError(
|
|
767
777
|
"API_SAIL_TIMEOUT",
|
|
768
778
|
`Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
|
|
769
|
-
{ timeoutMs, method, path }
|
|
779
|
+
{ timeoutMs, method, path },
|
|
780
|
+
this.id
|
|
770
781
|
);
|
|
771
782
|
}
|
|
772
783
|
throw error;
|
|
@@ -779,7 +790,8 @@ var SailAgent = class {
|
|
|
779
790
|
throw new OwneyError(
|
|
780
791
|
"API_SAIL_ERROR",
|
|
781
792
|
`Sail API error ${res.status}: ${text}`,
|
|
782
|
-
{ statusCode: res.status, method, path, responseBody: text }
|
|
793
|
+
{ statusCode: res.status, method, path, responseBody: text },
|
|
794
|
+
this.id
|
|
783
795
|
);
|
|
784
796
|
}
|
|
785
797
|
return res.json();
|
|
@@ -818,7 +830,9 @@ var SailAgent = class {
|
|
|
818
830
|
if (!state.walletAddress) {
|
|
819
831
|
throw new OwneyError(
|
|
820
832
|
"WALLET_ADDRESS_REQUIRED",
|
|
821
|
-
"Wallet address is required. Call sdk.connect(provider) first."
|
|
833
|
+
"Wallet address is required. Call sdk.connect(provider) first.",
|
|
834
|
+
void 0,
|
|
835
|
+
this.id
|
|
822
836
|
);
|
|
823
837
|
}
|
|
824
838
|
this.connectPromise = (async () => {
|
|
@@ -855,7 +869,9 @@ var SailAgent = class {
|
|
|
855
869
|
if (!this.smartWalletAddress) {
|
|
856
870
|
throw new OwneyError(
|
|
857
871
|
"NOT_CONNECTED",
|
|
858
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
872
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
873
|
+
void 0,
|
|
874
|
+
this.id
|
|
859
875
|
);
|
|
860
876
|
}
|
|
861
877
|
return {
|
|
@@ -884,7 +900,9 @@ var SailAgent = class {
|
|
|
884
900
|
if (!depositCallback) {
|
|
885
901
|
throw new OwneyError(
|
|
886
902
|
"DEPOSIT_CALLBACK_REQUIRED",
|
|
887
|
-
"depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
|
|
903
|
+
"depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash.",
|
|
904
|
+
void 0,
|
|
905
|
+
this.id
|
|
888
906
|
);
|
|
889
907
|
}
|
|
890
908
|
try {
|
|
@@ -894,7 +912,9 @@ var SailAgent = class {
|
|
|
894
912
|
if (!resolvedTokenAddress) {
|
|
895
913
|
throw new OwneyError(
|
|
896
914
|
"DEPOSIT_NO_PERMITTED_TOKENS",
|
|
897
|
-
"No permitted tokens available for deposit."
|
|
915
|
+
"No permitted tokens available for deposit.",
|
|
916
|
+
void 0,
|
|
917
|
+
this.id
|
|
898
918
|
);
|
|
899
919
|
}
|
|
900
920
|
await this.executePreHooks(resolvedTokenAddress, amount, chainId);
|
|
@@ -902,7 +922,9 @@ var SailAgent = class {
|
|
|
902
922
|
if (!txHash) {
|
|
903
923
|
throw new OwneyError(
|
|
904
924
|
"DEPOSIT_CALLBACK_INVALID",
|
|
905
|
-
"depositCallback must return a transaction hash (txHash) for Sail deposits."
|
|
925
|
+
"depositCallback must return a transaction hash (txHash) for Sail deposits.",
|
|
926
|
+
void 0,
|
|
927
|
+
this.id
|
|
906
928
|
);
|
|
907
929
|
}
|
|
908
930
|
const postResult = await this.executePostHooks(
|
|
@@ -950,14 +972,29 @@ var SailAgent = class {
|
|
|
950
972
|
);
|
|
951
973
|
}
|
|
952
974
|
// --- IAgent: Fund operations ---
|
|
953
|
-
async withdraw(state, chainId, amount) {
|
|
975
|
+
async withdraw(state, chainId, token, amount) {
|
|
954
976
|
await this.connectFull(state, chainId);
|
|
955
977
|
const withdrawInfo = await this.getWithdrawInfo();
|
|
956
|
-
const
|
|
978
|
+
const selectedTokenAddress = SupportedAssets.find(
|
|
979
|
+
(a) => a.symbol === token && a.chainId === chainId
|
|
980
|
+
);
|
|
981
|
+
if (!selectedTokenAddress) {
|
|
982
|
+
throw new OwneyError(
|
|
983
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
984
|
+
"Unsupported asset.",
|
|
985
|
+
void 0,
|
|
986
|
+
this.id
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
const tokenAddress = withdrawInfo.permittedTokens.find(
|
|
990
|
+
(t) => t.address === selectedTokenAddress.address
|
|
991
|
+
)?.address;
|
|
957
992
|
if (!tokenAddress) {
|
|
958
993
|
throw new OwneyError(
|
|
959
994
|
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
960
|
-
"No permitted tokens available for withdrawal."
|
|
995
|
+
"No permitted tokens available for withdrawal.",
|
|
996
|
+
void 0,
|
|
997
|
+
this.id
|
|
961
998
|
);
|
|
962
999
|
}
|
|
963
1000
|
const resolvedAmount = amount ?? withdrawInfo.currentBalance;
|
|
@@ -977,7 +1014,10 @@ var SailAgent = class {
|
|
|
977
1014
|
return mapWithdraw2({
|
|
978
1015
|
txHash: raw.transactionHash,
|
|
979
1016
|
type: amount ? "partial" : "full",
|
|
980
|
-
amount:
|
|
1017
|
+
amount: (0, import_viem.parseUnits)(
|
|
1018
|
+
resolvedAmount,
|
|
1019
|
+
selectedTokenAddress.decimals
|
|
1020
|
+
).toString()
|
|
981
1021
|
});
|
|
982
1022
|
}
|
|
983
1023
|
// --- IAgent: Portfolio reads ---
|
|
@@ -1081,7 +1121,8 @@ var SailAgent = class {
|
|
|
1081
1121
|
throw new OwneyError(
|
|
1082
1122
|
"VALIDATION_INVALID_DAYS",
|
|
1083
1123
|
`Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
|
|
1084
|
-
{ days }
|
|
1124
|
+
{ days },
|
|
1125
|
+
this.id
|
|
1085
1126
|
);
|
|
1086
1127
|
}
|
|
1087
1128
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1188,7 +1229,65 @@ async function fetchAgentKeys(apiKey) {
|
|
|
1188
1229
|
return json.data;
|
|
1189
1230
|
}
|
|
1190
1231
|
|
|
1232
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
1233
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
1234
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
1235
|
+
agent,
|
|
1236
|
+
balance,
|
|
1237
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
1238
|
+
}));
|
|
1239
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
1240
|
+
let remainder = requested - assigned;
|
|
1241
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
1242
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
1243
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
1244
|
+
});
|
|
1245
|
+
for (const p of byHeadroom) {
|
|
1246
|
+
if (remainder === 0n)
|
|
1247
|
+
break;
|
|
1248
|
+
const headroom = p.balance - p.planned;
|
|
1249
|
+
if (headroom <= 0n)
|
|
1250
|
+
continue;
|
|
1251
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
1252
|
+
p.planned += take;
|
|
1253
|
+
remainder -= take;
|
|
1254
|
+
}
|
|
1255
|
+
return plans;
|
|
1256
|
+
}
|
|
1257
|
+
function redistributeShare(plans, fromIndex, amount) {
|
|
1258
|
+
const candidates = plans.slice(fromIndex + 1).filter((c) => c.balance - c.planned > 0n);
|
|
1259
|
+
const totalHeadroom = candidates.reduce(
|
|
1260
|
+
(s, c) => s + (c.balance - c.planned),
|
|
1261
|
+
0n
|
|
1262
|
+
);
|
|
1263
|
+
if (totalHeadroom === 0n)
|
|
1264
|
+
return;
|
|
1265
|
+
let distributed = 0n;
|
|
1266
|
+
for (const c of candidates) {
|
|
1267
|
+
const headroom = c.balance - c.planned;
|
|
1268
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
1269
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
1270
|
+
c.planned += give;
|
|
1271
|
+
distributed += give;
|
|
1272
|
+
}
|
|
1273
|
+
let leftover = amount - distributed;
|
|
1274
|
+
for (const c of candidates) {
|
|
1275
|
+
if (leftover === 0n)
|
|
1276
|
+
break;
|
|
1277
|
+
const headroom = c.balance - c.planned;
|
|
1278
|
+
if (headroom <= 0n)
|
|
1279
|
+
continue;
|
|
1280
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
1281
|
+
c.planned += take;
|
|
1282
|
+
leftover -= take;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function sumWithdrawnAmount(results) {
|
|
1286
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1191
1289
|
// src/client.ts
|
|
1290
|
+
var import_viem2 = require("viem");
|
|
1192
1291
|
var OwneySDK = class {
|
|
1193
1292
|
agents = /* @__PURE__ */ new Map();
|
|
1194
1293
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1464,7 +1563,8 @@ var OwneySDK = class {
|
|
|
1464
1563
|
throw new OwneyError(
|
|
1465
1564
|
"ASSET_UNSUPPORTED",
|
|
1466
1565
|
`Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
|
|
1467
|
-
{ agentId: agent.id, asset, chainId }
|
|
1566
|
+
{ agentId: agent.id, asset, chainId },
|
|
1567
|
+
agent.id
|
|
1468
1568
|
);
|
|
1469
1569
|
}
|
|
1470
1570
|
}
|
|
@@ -1498,34 +1598,99 @@ var OwneySDK = class {
|
|
|
1498
1598
|
const { asset, amount, agentId } = options;
|
|
1499
1599
|
const state = this.requireState();
|
|
1500
1600
|
const chainId = this.requireChainId();
|
|
1601
|
+
const token = asset;
|
|
1602
|
+
const assetInfo = SupportedAssets.find(
|
|
1603
|
+
(a) => a.chainId === chainId && a.symbol === token
|
|
1604
|
+
);
|
|
1605
|
+
if (!assetInfo) {
|
|
1606
|
+
throw new OwneyError(
|
|
1607
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
1608
|
+
"Asset selected is not supported"
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1501
1611
|
if (agentId) {
|
|
1502
1612
|
const agent = this.getAgent(agentId);
|
|
1503
1613
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1504
|
-
return agent.withdraw(state, chainId, amount);
|
|
1614
|
+
return agent.withdraw(state, chainId, token, amount);
|
|
1505
1615
|
}
|
|
1506
1616
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1507
1617
|
if (!amount) {
|
|
1508
1618
|
const results2 = {};
|
|
1509
1619
|
for (const agent of eligibleAgents) {
|
|
1510
|
-
results2[agent.id] = await agent.withdraw(state, chainId);
|
|
1620
|
+
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
1511
1621
|
}
|
|
1512
1622
|
return { agentResult: results2 };
|
|
1513
1623
|
}
|
|
1624
|
+
const requested = BigInt(amount);
|
|
1625
|
+
const aggregated = await this.getBalances();
|
|
1626
|
+
const balances = eligibleAgents.map((agent) => {
|
|
1627
|
+
const agentBalance = aggregated.agentBalances[agent.id];
|
|
1628
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
1629
|
+
(t) => t.chainId === chainId && t.asset === asset
|
|
1630
|
+
);
|
|
1631
|
+
if (!tokenBalance)
|
|
1632
|
+
return { agent, balance: 0n };
|
|
1633
|
+
const balance = (0, import_viem2.parseUnits)(tokenBalance.amount, assetInfo.decimals);
|
|
1634
|
+
return { agent, balance };
|
|
1635
|
+
});
|
|
1636
|
+
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
1637
|
+
if (totalAvailable < requested) {
|
|
1638
|
+
throw new OwneyError(
|
|
1639
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
1640
|
+
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
1641
|
+
{
|
|
1642
|
+
asset,
|
|
1643
|
+
requested: amount,
|
|
1644
|
+
available: totalAvailable.toString()
|
|
1645
|
+
}
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
const plans = planProportionalShares(balances, requested, totalAvailable);
|
|
1514
1649
|
const results = {};
|
|
1515
|
-
|
|
1516
|
-
for (
|
|
1650
|
+
const agentErrors = {};
|
|
1651
|
+
for (let i = 0; i < plans.length; i++) {
|
|
1652
|
+
const p = plans[i];
|
|
1653
|
+
if (p.planned === 0n)
|
|
1654
|
+
continue;
|
|
1517
1655
|
try {
|
|
1518
|
-
results[agent.id] = await agent.withdraw(
|
|
1519
|
-
|
|
1656
|
+
results[p.agent.id] = await p.agent.withdraw(
|
|
1657
|
+
state,
|
|
1658
|
+
chainId,
|
|
1659
|
+
token,
|
|
1660
|
+
p.agent.id === "sail" ? (0, import_viem2.formatUnits)(p.planned, assetInfo.decimals) : p.planned.toString()
|
|
1661
|
+
);
|
|
1520
1662
|
} catch (err) {
|
|
1521
|
-
|
|
1663
|
+
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
1664
|
+
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
1665
|
+
redistributeShare(plans, i, p.planned);
|
|
1522
1666
|
}
|
|
1523
1667
|
}
|
|
1524
|
-
if (
|
|
1525
|
-
throw
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1668
|
+
if (Object.keys(results).length === 0) {
|
|
1669
|
+
throw new OwneyError(
|
|
1670
|
+
"WITHDRAW_ALL_FAILED",
|
|
1671
|
+
`Failed to withdraw "${amount}" of "${asset}" from all eligible agents.`,
|
|
1672
|
+
{
|
|
1673
|
+
asset,
|
|
1674
|
+
amount,
|
|
1675
|
+
agentErrors
|
|
1676
|
+
}
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
const withdrawn = sumWithdrawnAmount(results);
|
|
1680
|
+
if (withdrawn < requested) {
|
|
1681
|
+
throw new OwneyError(
|
|
1682
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
1683
|
+
`Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
|
|
1684
|
+
{
|
|
1685
|
+
asset,
|
|
1686
|
+
requested: amount,
|
|
1687
|
+
withdrawn: withdrawn.toString(),
|
|
1688
|
+
partialResults: results,
|
|
1689
|
+
agentErrors
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
return { agentResult: results };
|
|
1529
1694
|
}
|
|
1530
1695
|
// --- Portfolio reads ---
|
|
1531
1696
|
/**
|
|
@@ -1550,7 +1715,10 @@ var OwneySDK = class {
|
|
|
1550
1715
|
})
|
|
1551
1716
|
);
|
|
1552
1717
|
let successCount = 0;
|
|
1553
|
-
|
|
1718
|
+
const agentErrors = {};
|
|
1719
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1720
|
+
const settledResult = balanceResults[i];
|
|
1721
|
+
const [agentId2] = entries[i];
|
|
1554
1722
|
if (settledResult.status === "fulfilled") {
|
|
1555
1723
|
const [id, b] = settledResult.value;
|
|
1556
1724
|
results[id] = b;
|
|
@@ -1558,12 +1726,14 @@ var OwneySDK = class {
|
|
|
1558
1726
|
successCount += 1;
|
|
1559
1727
|
continue;
|
|
1560
1728
|
}
|
|
1561
|
-
|
|
1729
|
+
const reason = settledResult.reason;
|
|
1730
|
+
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
1562
1731
|
}
|
|
1563
1732
|
if (successCount === 0) {
|
|
1564
1733
|
throw new OwneyError(
|
|
1565
1734
|
"BALANCE_ALL_FAILED",
|
|
1566
|
-
"Failed to fetch balances for all active agents."
|
|
1735
|
+
"Failed to fetch balances for all active agents.",
|
|
1736
|
+
{ agentErrors }
|
|
1567
1737
|
);
|
|
1568
1738
|
}
|
|
1569
1739
|
return {
|
|
@@ -1658,10 +1828,6 @@ var OwneySDK = class {
|
|
|
1658
1828
|
totalApy += agentApy * weight;
|
|
1659
1829
|
}
|
|
1660
1830
|
}
|
|
1661
|
-
console.log("account apy", {
|
|
1662
|
-
totalApy: String(totalApy),
|
|
1663
|
-
agentApy: results
|
|
1664
|
-
});
|
|
1665
1831
|
return {
|
|
1666
1832
|
totalApy: String(totalApy),
|
|
1667
1833
|
agentApy: results
|
|
@@ -1802,20 +1968,26 @@ var OwneySDK = class {
|
|
|
1802
1968
|
})
|
|
1803
1969
|
);
|
|
1804
1970
|
let successCount = 0;
|
|
1805
|
-
|
|
1971
|
+
const agentErrors = {};
|
|
1972
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1973
|
+
const settledResult = balanceResults[i];
|
|
1974
|
+
const [agentId] = entries[i];
|
|
1806
1975
|
if (settledResult.status === "fulfilled") {
|
|
1807
1976
|
const [id, bal] = settledResult.value;
|
|
1808
1977
|
balances[id] = bal;
|
|
1809
1978
|
totalBalance += bal;
|
|
1810
1979
|
successCount += 1;
|
|
1811
1980
|
} else {
|
|
1812
|
-
|
|
1981
|
+
const reason = settledResult.reason;
|
|
1982
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
1983
|
+
console.error(`getAgentAllocation agent "${agentId}" failed:`, reason);
|
|
1813
1984
|
}
|
|
1814
1985
|
}
|
|
1815
1986
|
if (successCount === 0) {
|
|
1816
1987
|
throw new OwneyError(
|
|
1817
1988
|
"ALLOCATION_ALL_FAILED",
|
|
1818
|
-
"Failed to fetch allocation: all agents failed."
|
|
1989
|
+
"Failed to fetch allocation: all agents failed.",
|
|
1990
|
+
{ agentErrors }
|
|
1819
1991
|
);
|
|
1820
1992
|
}
|
|
1821
1993
|
const allocation = {};
|
package/dist/index.d.cts
CHANGED
|
@@ -28,7 +28,7 @@ interface IAgent {
|
|
|
28
28
|
disconnect(): Promise<void>;
|
|
29
29
|
activateAgent(state: ConnectionState, chainId: number): Promise<void>;
|
|
30
30
|
deposit(state: ConnectionState, chainId: number, amount: string, depositCallback?: DepositCallback): Promise<OwneyDepositResult>;
|
|
31
|
-
withdraw(state: ConnectionState, chainId: number, amount?: string): Promise<AgentWithdrawResult>;
|
|
31
|
+
withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
|
|
32
32
|
getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
|
|
33
33
|
getEarnings(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
|
|
34
34
|
getAccountApy(state: ConnectionState, chainId: number, days: DailyApyDays): Promise<AccountAgentApy>;
|
|
@@ -341,11 +341,12 @@ declare class OwneySDK {
|
|
|
341
341
|
getAgentAllocation(): Promise<OwneyAgentAllocation>;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "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" | "WITHDRAW_NO_PERMITTED_TOKENS" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
344
|
+
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "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" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
345
345
|
declare class OwneyError extends Error {
|
|
346
346
|
readonly code: OwneyErrorCode;
|
|
347
347
|
readonly details?: Record<string, unknown>;
|
|
348
|
-
|
|
348
|
+
readonly agentId?: string;
|
|
349
|
+
constructor(code: OwneyErrorCode, message: string, details?: Record<string, unknown>, agentId?: string);
|
|
349
350
|
}
|
|
350
351
|
declare class AgentNotFoundError extends OwneyError {
|
|
351
352
|
constructor(agentId: string, available: string[]);
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ interface IAgent {
|
|
|
28
28
|
disconnect(): Promise<void>;
|
|
29
29
|
activateAgent(state: ConnectionState, chainId: number): Promise<void>;
|
|
30
30
|
deposit(state: ConnectionState, chainId: number, amount: string, depositCallback?: DepositCallback): Promise<OwneyDepositResult>;
|
|
31
|
-
withdraw(state: ConnectionState, chainId: number, amount?: string): Promise<AgentWithdrawResult>;
|
|
31
|
+
withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
|
|
32
32
|
getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
|
|
33
33
|
getEarnings(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
|
|
34
34
|
getAccountApy(state: ConnectionState, chainId: number, days: DailyApyDays): Promise<AccountAgentApy>;
|
|
@@ -341,11 +341,12 @@ declare class OwneySDK {
|
|
|
341
341
|
getAgentAllocation(): Promise<OwneyAgentAllocation>;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "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" | "WITHDRAW_NO_PERMITTED_TOKENS" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
344
|
+
type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "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" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
|
|
345
345
|
declare class OwneyError extends Error {
|
|
346
346
|
readonly code: OwneyErrorCode;
|
|
347
347
|
readonly details?: Record<string, unknown>;
|
|
348
|
-
|
|
348
|
+
readonly agentId?: string;
|
|
349
|
+
constructor(code: OwneyErrorCode, message: string, details?: Record<string, unknown>, agentId?: string);
|
|
349
350
|
}
|
|
350
351
|
declare class AgentNotFoundError extends OwneyError {
|
|
351
352
|
constructor(agentId: string, available: string[]);
|
package/dist/index.js
CHANGED
|
@@ -12,17 +12,20 @@ var SupportedAssets = [
|
|
|
12
12
|
{
|
|
13
13
|
address: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
|
|
14
14
|
symbol: "USDC",
|
|
15
|
-
chainId: 8453
|
|
15
|
+
chainId: 8453,
|
|
16
|
+
decimals: 6
|
|
16
17
|
},
|
|
17
18
|
{
|
|
18
19
|
address: `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`,
|
|
19
20
|
symbol: "USDC",
|
|
20
|
-
chainId: 42161
|
|
21
|
+
chainId: 42161,
|
|
22
|
+
decimals: 6
|
|
21
23
|
},
|
|
22
24
|
{
|
|
23
25
|
address: `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9`,
|
|
24
26
|
symbol: "USDT",
|
|
25
|
-
chainId: 42161
|
|
27
|
+
chainId: 42161,
|
|
28
|
+
decimals: 6
|
|
26
29
|
}
|
|
27
30
|
];
|
|
28
31
|
|
|
@@ -30,11 +33,13 @@ var SupportedAssets = [
|
|
|
30
33
|
var OwneyError = class extends Error {
|
|
31
34
|
code;
|
|
32
35
|
details;
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
agentId;
|
|
37
|
+
constructor(code, message, details, agentId) {
|
|
38
|
+
super(agentId ? `[agent:${agentId}] ${message}` : message);
|
|
35
39
|
this.name = "OwneyError";
|
|
36
40
|
this.code = code;
|
|
37
41
|
this.details = details;
|
|
42
|
+
this.agentId = agentId;
|
|
38
43
|
}
|
|
39
44
|
};
|
|
40
45
|
var AgentNotFoundError = class extends OwneyError {
|
|
@@ -42,7 +47,8 @@ var AgentNotFoundError = class extends OwneyError {
|
|
|
42
47
|
super(
|
|
43
48
|
"AGENT_NOT_FOUND",
|
|
44
49
|
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
45
|
-
{ agentId, available }
|
|
50
|
+
{ agentId, available },
|
|
51
|
+
agentId
|
|
46
52
|
);
|
|
47
53
|
this.name = "AgentNotFoundError";
|
|
48
54
|
}
|
|
@@ -309,7 +315,9 @@ var ZyfaiAgent = class {
|
|
|
309
315
|
if (!this.connectedAddress) {
|
|
310
316
|
throw new OwneyError(
|
|
311
317
|
"NOT_CONNECTED",
|
|
312
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
318
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
319
|
+
void 0,
|
|
320
|
+
this.id
|
|
313
321
|
);
|
|
314
322
|
}
|
|
315
323
|
return this.connectedAddress;
|
|
@@ -415,13 +423,14 @@ var ZyfaiAgent = class {
|
|
|
415
423
|
}
|
|
416
424
|
}
|
|
417
425
|
// --- IAgent: Fund operations ---
|
|
418
|
-
async withdraw(state, chainId, amount) {
|
|
426
|
+
async withdraw(state, chainId, token, amount) {
|
|
419
427
|
await this.connectFull(state, chainId);
|
|
420
428
|
const validChainId = isValidChainId(chainId);
|
|
421
429
|
const raw = await this.sdk.withdrawFunds(
|
|
422
430
|
this.getAddress(),
|
|
423
431
|
validChainId,
|
|
424
|
-
amount
|
|
432
|
+
amount,
|
|
433
|
+
token
|
|
425
434
|
);
|
|
426
435
|
return mapWithdraw(raw);
|
|
427
436
|
}
|
|
@@ -679,6 +688,7 @@ function remapApyPerAssetToSymbols(raw) {
|
|
|
679
688
|
}
|
|
680
689
|
|
|
681
690
|
// src/agents/sail/sail.agent.ts
|
|
691
|
+
import { parseUnits } from "viem";
|
|
682
692
|
var SAIL_API_BASE = "https://app.sail.money";
|
|
683
693
|
var PROJECT_ID = "sail";
|
|
684
694
|
var PAGE_ID = "home";
|
|
@@ -736,7 +746,8 @@ var SailAgent = class {
|
|
|
736
746
|
throw new OwneyError(
|
|
737
747
|
"API_SAIL_TIMEOUT",
|
|
738
748
|
`Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
|
|
739
|
-
{ timeoutMs, method, path }
|
|
749
|
+
{ timeoutMs, method, path },
|
|
750
|
+
this.id
|
|
740
751
|
);
|
|
741
752
|
}
|
|
742
753
|
throw error;
|
|
@@ -749,7 +760,8 @@ var SailAgent = class {
|
|
|
749
760
|
throw new OwneyError(
|
|
750
761
|
"API_SAIL_ERROR",
|
|
751
762
|
`Sail API error ${res.status}: ${text}`,
|
|
752
|
-
{ statusCode: res.status, method, path, responseBody: text }
|
|
763
|
+
{ statusCode: res.status, method, path, responseBody: text },
|
|
764
|
+
this.id
|
|
753
765
|
);
|
|
754
766
|
}
|
|
755
767
|
return res.json();
|
|
@@ -788,7 +800,9 @@ var SailAgent = class {
|
|
|
788
800
|
if (!state.walletAddress) {
|
|
789
801
|
throw new OwneyError(
|
|
790
802
|
"WALLET_ADDRESS_REQUIRED",
|
|
791
|
-
"Wallet address is required. Call sdk.connect(provider) first."
|
|
803
|
+
"Wallet address is required. Call sdk.connect(provider) first.",
|
|
804
|
+
void 0,
|
|
805
|
+
this.id
|
|
792
806
|
);
|
|
793
807
|
}
|
|
794
808
|
this.connectPromise = (async () => {
|
|
@@ -825,7 +839,9 @@ var SailAgent = class {
|
|
|
825
839
|
if (!this.smartWalletAddress) {
|
|
826
840
|
throw new OwneyError(
|
|
827
841
|
"NOT_CONNECTED",
|
|
828
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
842
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
843
|
+
void 0,
|
|
844
|
+
this.id
|
|
829
845
|
);
|
|
830
846
|
}
|
|
831
847
|
return {
|
|
@@ -854,7 +870,9 @@ var SailAgent = class {
|
|
|
854
870
|
if (!depositCallback) {
|
|
855
871
|
throw new OwneyError(
|
|
856
872
|
"DEPOSIT_CALLBACK_REQUIRED",
|
|
857
|
-
"depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
|
|
873
|
+
"depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash.",
|
|
874
|
+
void 0,
|
|
875
|
+
this.id
|
|
858
876
|
);
|
|
859
877
|
}
|
|
860
878
|
try {
|
|
@@ -864,7 +882,9 @@ var SailAgent = class {
|
|
|
864
882
|
if (!resolvedTokenAddress) {
|
|
865
883
|
throw new OwneyError(
|
|
866
884
|
"DEPOSIT_NO_PERMITTED_TOKENS",
|
|
867
|
-
"No permitted tokens available for deposit."
|
|
885
|
+
"No permitted tokens available for deposit.",
|
|
886
|
+
void 0,
|
|
887
|
+
this.id
|
|
868
888
|
);
|
|
869
889
|
}
|
|
870
890
|
await this.executePreHooks(resolvedTokenAddress, amount, chainId);
|
|
@@ -872,7 +892,9 @@ var SailAgent = class {
|
|
|
872
892
|
if (!txHash) {
|
|
873
893
|
throw new OwneyError(
|
|
874
894
|
"DEPOSIT_CALLBACK_INVALID",
|
|
875
|
-
"depositCallback must return a transaction hash (txHash) for Sail deposits."
|
|
895
|
+
"depositCallback must return a transaction hash (txHash) for Sail deposits.",
|
|
896
|
+
void 0,
|
|
897
|
+
this.id
|
|
876
898
|
);
|
|
877
899
|
}
|
|
878
900
|
const postResult = await this.executePostHooks(
|
|
@@ -920,14 +942,29 @@ var SailAgent = class {
|
|
|
920
942
|
);
|
|
921
943
|
}
|
|
922
944
|
// --- IAgent: Fund operations ---
|
|
923
|
-
async withdraw(state, chainId, amount) {
|
|
945
|
+
async withdraw(state, chainId, token, amount) {
|
|
924
946
|
await this.connectFull(state, chainId);
|
|
925
947
|
const withdrawInfo = await this.getWithdrawInfo();
|
|
926
|
-
const
|
|
948
|
+
const selectedTokenAddress = SupportedAssets.find(
|
|
949
|
+
(a) => a.symbol === token && a.chainId === chainId
|
|
950
|
+
);
|
|
951
|
+
if (!selectedTokenAddress) {
|
|
952
|
+
throw new OwneyError(
|
|
953
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
954
|
+
"Unsupported asset.",
|
|
955
|
+
void 0,
|
|
956
|
+
this.id
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
const tokenAddress = withdrawInfo.permittedTokens.find(
|
|
960
|
+
(t) => t.address === selectedTokenAddress.address
|
|
961
|
+
)?.address;
|
|
927
962
|
if (!tokenAddress) {
|
|
928
963
|
throw new OwneyError(
|
|
929
964
|
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
930
|
-
"No permitted tokens available for withdrawal."
|
|
965
|
+
"No permitted tokens available for withdrawal.",
|
|
966
|
+
void 0,
|
|
967
|
+
this.id
|
|
931
968
|
);
|
|
932
969
|
}
|
|
933
970
|
const resolvedAmount = amount ?? withdrawInfo.currentBalance;
|
|
@@ -947,7 +984,10 @@ var SailAgent = class {
|
|
|
947
984
|
return mapWithdraw2({
|
|
948
985
|
txHash: raw.transactionHash,
|
|
949
986
|
type: amount ? "partial" : "full",
|
|
950
|
-
amount:
|
|
987
|
+
amount: parseUnits(
|
|
988
|
+
resolvedAmount,
|
|
989
|
+
selectedTokenAddress.decimals
|
|
990
|
+
).toString()
|
|
951
991
|
});
|
|
952
992
|
}
|
|
953
993
|
// --- IAgent: Portfolio reads ---
|
|
@@ -1051,7 +1091,8 @@ var SailAgent = class {
|
|
|
1051
1091
|
throw new OwneyError(
|
|
1052
1092
|
"VALIDATION_INVALID_DAYS",
|
|
1053
1093
|
`Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
|
|
1054
|
-
{ days }
|
|
1094
|
+
{ days },
|
|
1095
|
+
this.id
|
|
1055
1096
|
);
|
|
1056
1097
|
}
|
|
1057
1098
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1158,7 +1199,65 @@ async function fetchAgentKeys(apiKey) {
|
|
|
1158
1199
|
return json.data;
|
|
1159
1200
|
}
|
|
1160
1201
|
|
|
1202
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
1203
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
1204
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
1205
|
+
agent,
|
|
1206
|
+
balance,
|
|
1207
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
1208
|
+
}));
|
|
1209
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
1210
|
+
let remainder = requested - assigned;
|
|
1211
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
1212
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
1213
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
1214
|
+
});
|
|
1215
|
+
for (const p of byHeadroom) {
|
|
1216
|
+
if (remainder === 0n)
|
|
1217
|
+
break;
|
|
1218
|
+
const headroom = p.balance - p.planned;
|
|
1219
|
+
if (headroom <= 0n)
|
|
1220
|
+
continue;
|
|
1221
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
1222
|
+
p.planned += take;
|
|
1223
|
+
remainder -= take;
|
|
1224
|
+
}
|
|
1225
|
+
return plans;
|
|
1226
|
+
}
|
|
1227
|
+
function redistributeShare(plans, fromIndex, amount) {
|
|
1228
|
+
const candidates = plans.slice(fromIndex + 1).filter((c) => c.balance - c.planned > 0n);
|
|
1229
|
+
const totalHeadroom = candidates.reduce(
|
|
1230
|
+
(s, c) => s + (c.balance - c.planned),
|
|
1231
|
+
0n
|
|
1232
|
+
);
|
|
1233
|
+
if (totalHeadroom === 0n)
|
|
1234
|
+
return;
|
|
1235
|
+
let distributed = 0n;
|
|
1236
|
+
for (const c of candidates) {
|
|
1237
|
+
const headroom = c.balance - c.planned;
|
|
1238
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
1239
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
1240
|
+
c.planned += give;
|
|
1241
|
+
distributed += give;
|
|
1242
|
+
}
|
|
1243
|
+
let leftover = amount - distributed;
|
|
1244
|
+
for (const c of candidates) {
|
|
1245
|
+
if (leftover === 0n)
|
|
1246
|
+
break;
|
|
1247
|
+
const headroom = c.balance - c.planned;
|
|
1248
|
+
if (headroom <= 0n)
|
|
1249
|
+
continue;
|
|
1250
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
1251
|
+
c.planned += take;
|
|
1252
|
+
leftover -= take;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
function sumWithdrawnAmount(results) {
|
|
1256
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1161
1259
|
// src/client.ts
|
|
1260
|
+
import { formatUnits, parseUnits as parseUnits2 } from "viem";
|
|
1162
1261
|
var OwneySDK = class {
|
|
1163
1262
|
agents = /* @__PURE__ */ new Map();
|
|
1164
1263
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1434,7 +1533,8 @@ var OwneySDK = class {
|
|
|
1434
1533
|
throw new OwneyError(
|
|
1435
1534
|
"ASSET_UNSUPPORTED",
|
|
1436
1535
|
`Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
|
|
1437
|
-
{ agentId: agent.id, asset, chainId }
|
|
1536
|
+
{ agentId: agent.id, asset, chainId },
|
|
1537
|
+
agent.id
|
|
1438
1538
|
);
|
|
1439
1539
|
}
|
|
1440
1540
|
}
|
|
@@ -1468,34 +1568,99 @@ var OwneySDK = class {
|
|
|
1468
1568
|
const { asset, amount, agentId } = options;
|
|
1469
1569
|
const state = this.requireState();
|
|
1470
1570
|
const chainId = this.requireChainId();
|
|
1571
|
+
const token = asset;
|
|
1572
|
+
const assetInfo = SupportedAssets.find(
|
|
1573
|
+
(a) => a.chainId === chainId && a.symbol === token
|
|
1574
|
+
);
|
|
1575
|
+
if (!assetInfo) {
|
|
1576
|
+
throw new OwneyError(
|
|
1577
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
1578
|
+
"Asset selected is not supported"
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1471
1581
|
if (agentId) {
|
|
1472
1582
|
const agent = this.getAgent(agentId);
|
|
1473
1583
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1474
|
-
return agent.withdraw(state, chainId, amount);
|
|
1584
|
+
return agent.withdraw(state, chainId, token, amount);
|
|
1475
1585
|
}
|
|
1476
1586
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1477
1587
|
if (!amount) {
|
|
1478
1588
|
const results2 = {};
|
|
1479
1589
|
for (const agent of eligibleAgents) {
|
|
1480
|
-
results2[agent.id] = await agent.withdraw(state, chainId);
|
|
1590
|
+
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
1481
1591
|
}
|
|
1482
1592
|
return { agentResult: results2 };
|
|
1483
1593
|
}
|
|
1594
|
+
const requested = BigInt(amount);
|
|
1595
|
+
const aggregated = await this.getBalances();
|
|
1596
|
+
const balances = eligibleAgents.map((agent) => {
|
|
1597
|
+
const agentBalance = aggregated.agentBalances[agent.id];
|
|
1598
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
1599
|
+
(t) => t.chainId === chainId && t.asset === asset
|
|
1600
|
+
);
|
|
1601
|
+
if (!tokenBalance)
|
|
1602
|
+
return { agent, balance: 0n };
|
|
1603
|
+
const balance = parseUnits2(tokenBalance.amount, assetInfo.decimals);
|
|
1604
|
+
return { agent, balance };
|
|
1605
|
+
});
|
|
1606
|
+
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
1607
|
+
if (totalAvailable < requested) {
|
|
1608
|
+
throw new OwneyError(
|
|
1609
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
1610
|
+
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
1611
|
+
{
|
|
1612
|
+
asset,
|
|
1613
|
+
requested: amount,
|
|
1614
|
+
available: totalAvailable.toString()
|
|
1615
|
+
}
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
const plans = planProportionalShares(balances, requested, totalAvailable);
|
|
1484
1619
|
const results = {};
|
|
1485
|
-
|
|
1486
|
-
for (
|
|
1620
|
+
const agentErrors = {};
|
|
1621
|
+
for (let i = 0; i < plans.length; i++) {
|
|
1622
|
+
const p = plans[i];
|
|
1623
|
+
if (p.planned === 0n)
|
|
1624
|
+
continue;
|
|
1487
1625
|
try {
|
|
1488
|
-
results[agent.id] = await agent.withdraw(
|
|
1489
|
-
|
|
1626
|
+
results[p.agent.id] = await p.agent.withdraw(
|
|
1627
|
+
state,
|
|
1628
|
+
chainId,
|
|
1629
|
+
token,
|
|
1630
|
+
p.agent.id === "sail" ? formatUnits(p.planned, assetInfo.decimals) : p.planned.toString()
|
|
1631
|
+
);
|
|
1490
1632
|
} catch (err) {
|
|
1491
|
-
|
|
1633
|
+
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
1634
|
+
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
1635
|
+
redistributeShare(plans, i, p.planned);
|
|
1492
1636
|
}
|
|
1493
1637
|
}
|
|
1494
|
-
if (
|
|
1495
|
-
throw
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1638
|
+
if (Object.keys(results).length === 0) {
|
|
1639
|
+
throw new OwneyError(
|
|
1640
|
+
"WITHDRAW_ALL_FAILED",
|
|
1641
|
+
`Failed to withdraw "${amount}" of "${asset}" from all eligible agents.`,
|
|
1642
|
+
{
|
|
1643
|
+
asset,
|
|
1644
|
+
amount,
|
|
1645
|
+
agentErrors
|
|
1646
|
+
}
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
const withdrawn = sumWithdrawnAmount(results);
|
|
1650
|
+
if (withdrawn < requested) {
|
|
1651
|
+
throw new OwneyError(
|
|
1652
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
1653
|
+
`Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
|
|
1654
|
+
{
|
|
1655
|
+
asset,
|
|
1656
|
+
requested: amount,
|
|
1657
|
+
withdrawn: withdrawn.toString(),
|
|
1658
|
+
partialResults: results,
|
|
1659
|
+
agentErrors
|
|
1660
|
+
}
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
return { agentResult: results };
|
|
1499
1664
|
}
|
|
1500
1665
|
// --- Portfolio reads ---
|
|
1501
1666
|
/**
|
|
@@ -1520,7 +1685,10 @@ var OwneySDK = class {
|
|
|
1520
1685
|
})
|
|
1521
1686
|
);
|
|
1522
1687
|
let successCount = 0;
|
|
1523
|
-
|
|
1688
|
+
const agentErrors = {};
|
|
1689
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1690
|
+
const settledResult = balanceResults[i];
|
|
1691
|
+
const [agentId2] = entries[i];
|
|
1524
1692
|
if (settledResult.status === "fulfilled") {
|
|
1525
1693
|
const [id, b] = settledResult.value;
|
|
1526
1694
|
results[id] = b;
|
|
@@ -1528,12 +1696,14 @@ var OwneySDK = class {
|
|
|
1528
1696
|
successCount += 1;
|
|
1529
1697
|
continue;
|
|
1530
1698
|
}
|
|
1531
|
-
|
|
1699
|
+
const reason = settledResult.reason;
|
|
1700
|
+
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
1532
1701
|
}
|
|
1533
1702
|
if (successCount === 0) {
|
|
1534
1703
|
throw new OwneyError(
|
|
1535
1704
|
"BALANCE_ALL_FAILED",
|
|
1536
|
-
"Failed to fetch balances for all active agents."
|
|
1705
|
+
"Failed to fetch balances for all active agents.",
|
|
1706
|
+
{ agentErrors }
|
|
1537
1707
|
);
|
|
1538
1708
|
}
|
|
1539
1709
|
return {
|
|
@@ -1628,10 +1798,6 @@ var OwneySDK = class {
|
|
|
1628
1798
|
totalApy += agentApy * weight;
|
|
1629
1799
|
}
|
|
1630
1800
|
}
|
|
1631
|
-
console.log("account apy", {
|
|
1632
|
-
totalApy: String(totalApy),
|
|
1633
|
-
agentApy: results
|
|
1634
|
-
});
|
|
1635
1801
|
return {
|
|
1636
1802
|
totalApy: String(totalApy),
|
|
1637
1803
|
agentApy: results
|
|
@@ -1772,20 +1938,26 @@ var OwneySDK = class {
|
|
|
1772
1938
|
})
|
|
1773
1939
|
);
|
|
1774
1940
|
let successCount = 0;
|
|
1775
|
-
|
|
1941
|
+
const agentErrors = {};
|
|
1942
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1943
|
+
const settledResult = balanceResults[i];
|
|
1944
|
+
const [agentId] = entries[i];
|
|
1776
1945
|
if (settledResult.status === "fulfilled") {
|
|
1777
1946
|
const [id, bal] = settledResult.value;
|
|
1778
1947
|
balances[id] = bal;
|
|
1779
1948
|
totalBalance += bal;
|
|
1780
1949
|
successCount += 1;
|
|
1781
1950
|
} else {
|
|
1782
|
-
|
|
1951
|
+
const reason = settledResult.reason;
|
|
1952
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
1953
|
+
console.error(`getAgentAllocation agent "${agentId}" failed:`, reason);
|
|
1783
1954
|
}
|
|
1784
1955
|
}
|
|
1785
1956
|
if (successCount === 0) {
|
|
1786
1957
|
throw new OwneyError(
|
|
1787
1958
|
"ALLOCATION_ALL_FAILED",
|
|
1788
|
-
"Failed to fetch allocation: all agents failed."
|
|
1959
|
+
"Failed to fetch allocation: all agents failed.",
|
|
1960
|
+
{ agentErrors }
|
|
1789
1961
|
);
|
|
1790
1962
|
}
|
|
1791
1963
|
const allocation = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owney/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"dev": "tsup --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@zyfai/sdk": "^0.2.30"
|
|
23
|
+
"@zyfai/sdk": "^0.2.30",
|
|
24
|
+
"viem": "^2.48.1"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"dotenv": "^17.3.1",
|