@owney/sdk 0.2.8 → 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 +159 -116
- package/dist/index.cjs +283 -58
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +283 -58
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,11 @@ interface IAgent {
|
|
|
24
24
|
readonly id: string;
|
|
25
25
|
readonly supportedChainIds: readonly OwneySupportedChainId[];
|
|
26
26
|
readonly supportedAssets: readonly AgentSupportedAssets[];
|
|
27
|
+
readonly minDepositAmount: string;
|
|
27
28
|
disconnect(): Promise<void>;
|
|
28
29
|
activateAgent(state: ConnectionState, chainId: number): Promise<void>;
|
|
29
30
|
deposit(state: ConnectionState, chainId: number, amount: string, depositCallback?: DepositCallback): Promise<OwneyDepositResult>;
|
|
30
|
-
withdraw(state: ConnectionState, chainId: number, amount?: string): Promise<AgentWithdrawResult>;
|
|
31
|
+
withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
|
|
31
32
|
getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
|
|
32
33
|
getEarnings(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
|
|
33
34
|
getAccountApy(state: ConnectionState, chainId: number, days: DailyApyDays): Promise<AccountAgentApy>;
|
|
@@ -196,10 +197,6 @@ interface AgentApy {
|
|
|
196
197
|
interface OwneyAgentApy {
|
|
197
198
|
agentApy: Record<AgentId, AgentApy>;
|
|
198
199
|
}
|
|
199
|
-
interface OwneyRouteResponse {
|
|
200
|
-
agent: AgentId;
|
|
201
|
-
chainId: number;
|
|
202
|
-
}
|
|
203
200
|
interface OwneyAgentAllocation {
|
|
204
201
|
agentAllocation: Record<AgentId, number>;
|
|
205
202
|
}
|
|
@@ -252,7 +249,7 @@ declare class OwneySDK {
|
|
|
252
249
|
activateAgent(chainId: number, agentId?: AgentId[]): Promise<void>;
|
|
253
250
|
/**
|
|
254
251
|
* Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
|
|
255
|
-
|
|
252
|
+
* Validates that the asset is supported and amount meets minimums for the target agent(s).
|
|
256
253
|
* @param options - Deposit parameters
|
|
257
254
|
* @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
|
|
258
255
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
@@ -263,6 +260,8 @@ declare class OwneySDK {
|
|
|
263
260
|
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
264
261
|
*/
|
|
265
262
|
deposit(options: DepositOptions): Promise<OwneyDepositResult | OwneyMultiDepositResult>;
|
|
263
|
+
private splitDepositAmount;
|
|
264
|
+
private validateMinDepositAmount;
|
|
266
265
|
private validateAssetSupport;
|
|
267
266
|
private getEligibleAgents;
|
|
268
267
|
/**
|
|
@@ -342,11 +341,12 @@ declare class OwneySDK {
|
|
|
342
341
|
getAgentAllocation(): Promise<OwneyAgentAllocation>;
|
|
343
342
|
}
|
|
344
343
|
|
|
345
|
-
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" | "
|
|
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";
|
|
346
345
|
declare class OwneyError extends Error {
|
|
347
346
|
readonly code: OwneyErrorCode;
|
|
348
347
|
readonly details?: Record<string, unknown>;
|
|
349
|
-
|
|
348
|
+
readonly agentId?: string;
|
|
349
|
+
constructor(code: OwneyErrorCode, message: string, details?: Record<string, unknown>, agentId?: string);
|
|
350
350
|
}
|
|
351
351
|
declare class AgentNotFoundError extends OwneyError {
|
|
352
352
|
constructor(agentId: string, available: string[]);
|
|
@@ -366,4 +366,4 @@ declare class AgentChainIncompatibleError extends OwneyError {
|
|
|
366
366
|
}>, connectedChainId: number);
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult,
|
|
369
|
+
export { AccountAgentApy, AccountApyOptions, AgentApy, AgentApyDetails, AgentBalance, AgentChainIncompatibleError, AgentEarnings, AgentHistoryEntry, AgentId, AgentNotFoundError, AgentSupportedAssets, AgentUserProfile, AgentWithdrawResult, AgentsApyOptions, Asset, ConnectionState, DailyApyDays, DepositCallback, DepositOptions, HistoryAction, HistoryFilters, HistoryOptions, HistoryTransaction, IAgent, NotConnectedError, OwneyAccountApy, OwneyAgentAllocation, OwneyAgentApy, OwneyAgentHistory, OwneyAgentStatus, OwneyBalances, OwneyDepositResult, OwneyEarnings, OwneyError, OwneyErrorCode, OwneyMultiDepositResult, OwneySDK, OwneySDKConfig, OwneySupportedChainId, OwneySupportedChains, OwneySupportedTokens, OwneyToken, OwneyUserProfile, OwneyWithdrawResult, RebalanceLog, WithdrawOptions };
|
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
|
}
|
|
@@ -273,8 +279,13 @@ function mapApyByStrategy(raw) {
|
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
// src/agents/zyfai/zyfai.agent.ts
|
|
282
|
+
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
283
|
+
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
284
|
+
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
285
|
+
};
|
|
276
286
|
var ZyfaiAgent = class {
|
|
277
287
|
id = "zyfai";
|
|
288
|
+
minDepositAmount = "0";
|
|
278
289
|
supportedChainIds = [8453, 42161];
|
|
279
290
|
supportedAssets = [
|
|
280
291
|
{
|
|
@@ -294,14 +305,19 @@ var ZyfaiAgent = class {
|
|
|
294
305
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
295
306
|
connectAuthPromise = null;
|
|
296
307
|
constructor(apiKey, rpcUrls) {
|
|
297
|
-
this.sdk = new ZyfaiSDK({
|
|
308
|
+
this.sdk = new ZyfaiSDK({
|
|
309
|
+
apiKey,
|
|
310
|
+
rpcUrls: rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS
|
|
311
|
+
});
|
|
298
312
|
}
|
|
299
313
|
// --- Private helpers ---
|
|
300
314
|
getAddress() {
|
|
301
315
|
if (!this.connectedAddress) {
|
|
302
316
|
throw new OwneyError(
|
|
303
317
|
"NOT_CONNECTED",
|
|
304
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
318
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
319
|
+
void 0,
|
|
320
|
+
this.id
|
|
305
321
|
);
|
|
306
322
|
}
|
|
307
323
|
return this.connectedAddress;
|
|
@@ -407,13 +423,14 @@ var ZyfaiAgent = class {
|
|
|
407
423
|
}
|
|
408
424
|
}
|
|
409
425
|
// --- IAgent: Fund operations ---
|
|
410
|
-
async withdraw(state, chainId, amount) {
|
|
426
|
+
async withdraw(state, chainId, token, amount) {
|
|
411
427
|
await this.connectFull(state, chainId);
|
|
412
428
|
const validChainId = isValidChainId(chainId);
|
|
413
429
|
const raw = await this.sdk.withdrawFunds(
|
|
414
430
|
this.getAddress(),
|
|
415
431
|
validChainId,
|
|
416
|
-
amount
|
|
432
|
+
amount,
|
|
433
|
+
token
|
|
417
434
|
);
|
|
418
435
|
return mapWithdraw(raw);
|
|
419
436
|
}
|
|
@@ -610,7 +627,7 @@ function mapHistory2(raw) {
|
|
|
610
627
|
rewards: "Earned"
|
|
611
628
|
};
|
|
612
629
|
const filtered = raw.result.items.filter(
|
|
613
|
-
(i) => i.
|
|
630
|
+
(i) => !(i.type === "execution" && (i.status === "failed" || !i.transactions.length))
|
|
614
631
|
);
|
|
615
632
|
return {
|
|
616
633
|
total: filtered.length,
|
|
@@ -671,12 +688,14 @@ function remapApyPerAssetToSymbols(raw) {
|
|
|
671
688
|
}
|
|
672
689
|
|
|
673
690
|
// src/agents/sail/sail.agent.ts
|
|
691
|
+
import { parseUnits } from "viem";
|
|
674
692
|
var SAIL_API_BASE = "https://app.sail.money";
|
|
675
693
|
var PROJECT_ID = "sail";
|
|
676
694
|
var PAGE_ID = "home";
|
|
677
695
|
var SAIL_PORTFOLIO_TIMEOUT_MS = 8e3;
|
|
678
696
|
var SailAgent = class {
|
|
679
697
|
id = "sail";
|
|
698
|
+
minDepositAmount = "4800000000";
|
|
680
699
|
supportedChainIds = [8453, 42161];
|
|
681
700
|
supportedAssets = [
|
|
682
701
|
{
|
|
@@ -727,7 +746,8 @@ var SailAgent = class {
|
|
|
727
746
|
throw new OwneyError(
|
|
728
747
|
"API_SAIL_TIMEOUT",
|
|
729
748
|
`Sail API timeout ${timeoutMs}ms: ${method} ${path}`,
|
|
730
|
-
{ timeoutMs, method, path }
|
|
749
|
+
{ timeoutMs, method, path },
|
|
750
|
+
this.id
|
|
731
751
|
);
|
|
732
752
|
}
|
|
733
753
|
throw error;
|
|
@@ -740,7 +760,8 @@ var SailAgent = class {
|
|
|
740
760
|
throw new OwneyError(
|
|
741
761
|
"API_SAIL_ERROR",
|
|
742
762
|
`Sail API error ${res.status}: ${text}`,
|
|
743
|
-
{ statusCode: res.status, method, path, responseBody: text }
|
|
763
|
+
{ statusCode: res.status, method, path, responseBody: text },
|
|
764
|
+
this.id
|
|
744
765
|
);
|
|
745
766
|
}
|
|
746
767
|
return res.json();
|
|
@@ -779,7 +800,9 @@ var SailAgent = class {
|
|
|
779
800
|
if (!state.walletAddress) {
|
|
780
801
|
throw new OwneyError(
|
|
781
802
|
"WALLET_ADDRESS_REQUIRED",
|
|
782
|
-
"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
|
|
783
806
|
);
|
|
784
807
|
}
|
|
785
808
|
this.connectPromise = (async () => {
|
|
@@ -816,7 +839,9 @@ var SailAgent = class {
|
|
|
816
839
|
if (!this.smartWalletAddress) {
|
|
817
840
|
throw new OwneyError(
|
|
818
841
|
"NOT_CONNECTED",
|
|
819
|
-
"Wallet not connected. Call sdk.connect() first."
|
|
842
|
+
"Wallet not connected. Call sdk.connect() first.",
|
|
843
|
+
void 0,
|
|
844
|
+
this.id
|
|
820
845
|
);
|
|
821
846
|
}
|
|
822
847
|
return {
|
|
@@ -845,7 +870,9 @@ var SailAgent = class {
|
|
|
845
870
|
if (!depositCallback) {
|
|
846
871
|
throw new OwneyError(
|
|
847
872
|
"DEPOSIT_CALLBACK_REQUIRED",
|
|
848
|
-
"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
|
|
849
876
|
);
|
|
850
877
|
}
|
|
851
878
|
try {
|
|
@@ -855,7 +882,9 @@ var SailAgent = class {
|
|
|
855
882
|
if (!resolvedTokenAddress) {
|
|
856
883
|
throw new OwneyError(
|
|
857
884
|
"DEPOSIT_NO_PERMITTED_TOKENS",
|
|
858
|
-
"No permitted tokens available for deposit."
|
|
885
|
+
"No permitted tokens available for deposit.",
|
|
886
|
+
void 0,
|
|
887
|
+
this.id
|
|
859
888
|
);
|
|
860
889
|
}
|
|
861
890
|
await this.executePreHooks(resolvedTokenAddress, amount, chainId);
|
|
@@ -863,7 +892,9 @@ var SailAgent = class {
|
|
|
863
892
|
if (!txHash) {
|
|
864
893
|
throw new OwneyError(
|
|
865
894
|
"DEPOSIT_CALLBACK_INVALID",
|
|
866
|
-
"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
|
|
867
898
|
);
|
|
868
899
|
}
|
|
869
900
|
const postResult = await this.executePostHooks(
|
|
@@ -911,14 +942,29 @@ var SailAgent = class {
|
|
|
911
942
|
);
|
|
912
943
|
}
|
|
913
944
|
// --- IAgent: Fund operations ---
|
|
914
|
-
async withdraw(state, chainId, amount) {
|
|
945
|
+
async withdraw(state, chainId, token, amount) {
|
|
915
946
|
await this.connectFull(state, chainId);
|
|
916
947
|
const withdrawInfo = await this.getWithdrawInfo();
|
|
917
|
-
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;
|
|
918
962
|
if (!tokenAddress) {
|
|
919
963
|
throw new OwneyError(
|
|
920
964
|
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
921
|
-
"No permitted tokens available for withdrawal."
|
|
965
|
+
"No permitted tokens available for withdrawal.",
|
|
966
|
+
void 0,
|
|
967
|
+
this.id
|
|
922
968
|
);
|
|
923
969
|
}
|
|
924
970
|
const resolvedAmount = amount ?? withdrawInfo.currentBalance;
|
|
@@ -938,7 +984,10 @@ var SailAgent = class {
|
|
|
938
984
|
return mapWithdraw2({
|
|
939
985
|
txHash: raw.transactionHash,
|
|
940
986
|
type: amount ? "partial" : "full",
|
|
941
|
-
amount:
|
|
987
|
+
amount: parseUnits(
|
|
988
|
+
resolvedAmount,
|
|
989
|
+
selectedTokenAddress.decimals
|
|
990
|
+
).toString()
|
|
942
991
|
});
|
|
943
992
|
}
|
|
944
993
|
// --- IAgent: Portfolio reads ---
|
|
@@ -1042,7 +1091,8 @@ var SailAgent = class {
|
|
|
1042
1091
|
throw new OwneyError(
|
|
1043
1092
|
"VALIDATION_INVALID_DAYS",
|
|
1044
1093
|
`Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`,
|
|
1045
|
-
{ days }
|
|
1094
|
+
{ days },
|
|
1095
|
+
this.id
|
|
1046
1096
|
);
|
|
1047
1097
|
}
|
|
1048
1098
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1149,7 +1199,65 @@ async function fetchAgentKeys(apiKey) {
|
|
|
1149
1199
|
return json.data;
|
|
1150
1200
|
}
|
|
1151
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
|
+
|
|
1152
1259
|
// src/client.ts
|
|
1260
|
+
import { formatUnits, parseUnits as parseUnits2 } from "viem";
|
|
1153
1261
|
var OwneySDK = class {
|
|
1154
1262
|
agents = /* @__PURE__ */ new Map();
|
|
1155
1263
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1342,7 +1450,7 @@ var OwneySDK = class {
|
|
|
1342
1450
|
}
|
|
1343
1451
|
/**
|
|
1344
1452
|
* Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
|
|
1345
|
-
|
|
1453
|
+
* Validates that the asset is supported and amount meets minimums for the target agent(s).
|
|
1346
1454
|
* @param options - Deposit parameters
|
|
1347
1455
|
* @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
|
|
1348
1456
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
@@ -1354,28 +1462,29 @@ var OwneySDK = class {
|
|
|
1354
1462
|
*/
|
|
1355
1463
|
async deposit(options) {
|
|
1356
1464
|
const { amount, asset, depositCallback, agentId } = options;
|
|
1465
|
+
if (BigInt(amount) === 0n) {
|
|
1466
|
+
return { agentResults: {} };
|
|
1467
|
+
}
|
|
1357
1468
|
const state = this.requireState();
|
|
1358
1469
|
const chainId = this.requireChainId();
|
|
1359
1470
|
if (agentId) {
|
|
1360
1471
|
const agent = this.getAgent(agentId);
|
|
1361
1472
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1473
|
+
this.validateMinDepositAmount(agent, amount);
|
|
1362
1474
|
return agent.deposit(state, chainId, amount, depositCallback);
|
|
1363
1475
|
}
|
|
1364
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1365
1476
|
const totalAmount = BigInt(amount);
|
|
1366
|
-
|
|
1477
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1478
|
+
const agentAmounts = this.splitDepositAmount(totalAmount, eligibleAgents);
|
|
1479
|
+
if (agentAmounts.length === 0) {
|
|
1367
1480
|
throw new OwneyError(
|
|
1368
|
-
"
|
|
1369
|
-
`Amount "${amount}"
|
|
1370
|
-
{ amount
|
|
1481
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
1482
|
+
`Amount "${amount}" cannot satisfy minimum deposit requirements for any eligible agent.`,
|
|
1483
|
+
{ amount }
|
|
1371
1484
|
);
|
|
1372
1485
|
}
|
|
1373
|
-
const perAgent = totalAmount / BigInt(eligibleAgents.length);
|
|
1374
|
-
const remainder = totalAmount % BigInt(eligibleAgents.length);
|
|
1375
1486
|
const agentResults = {};
|
|
1376
|
-
for (
|
|
1377
|
-
const agent = eligibleAgents[i];
|
|
1378
|
-
const agentAmount = i === eligibleAgents.length - 1 ? String(perAgent + remainder) : String(perAgent);
|
|
1487
|
+
for (const { agent, amount: agentAmount } of agentAmounts) {
|
|
1379
1488
|
agentResults[agent.id] = await agent.deposit(
|
|
1380
1489
|
state,
|
|
1381
1490
|
chainId,
|
|
@@ -1385,6 +1494,37 @@ var OwneySDK = class {
|
|
|
1385
1494
|
}
|
|
1386
1495
|
return { agentResults };
|
|
1387
1496
|
}
|
|
1497
|
+
splitDepositAmount(totalAmount, agents) {
|
|
1498
|
+
if (agents.length === 0)
|
|
1499
|
+
return [];
|
|
1500
|
+
const perAgent = totalAmount / BigInt(agents.length);
|
|
1501
|
+
const remainder = totalAmount % BigInt(agents.length);
|
|
1502
|
+
const splits = agents.map((agent, i) => ({
|
|
1503
|
+
agent,
|
|
1504
|
+
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
1505
|
+
}));
|
|
1506
|
+
const valid = splits.filter(
|
|
1507
|
+
(s) => s.amount >= BigInt(s.agent.minDepositAmount)
|
|
1508
|
+
);
|
|
1509
|
+
if (valid.length === agents.length) {
|
|
1510
|
+
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
1511
|
+
}
|
|
1512
|
+
return this.splitDepositAmount(totalAmount, valid.map((s) => s.agent));
|
|
1513
|
+
}
|
|
1514
|
+
validateMinDepositAmount(agent, amount) {
|
|
1515
|
+
const minDepositAmount = BigInt(agent.minDepositAmount);
|
|
1516
|
+
const parsedAmount = BigInt(amount);
|
|
1517
|
+
if (parsedAmount < minDepositAmount) {
|
|
1518
|
+
throw new OwneyError(
|
|
1519
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
1520
|
+
`Amount "${amount}" is below the required minimum deposit for agent "${agent.id}".`,
|
|
1521
|
+
{
|
|
1522
|
+
agentId: agent.id,
|
|
1523
|
+
amount
|
|
1524
|
+
}
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1388
1528
|
validateAssetSupport(agent, chainId, asset) {
|
|
1389
1529
|
const chainAssets = agent.supportedAssets.find(
|
|
1390
1530
|
(sa) => sa.chainId === chainId
|
|
@@ -1393,7 +1533,8 @@ var OwneySDK = class {
|
|
|
1393
1533
|
throw new OwneyError(
|
|
1394
1534
|
"ASSET_UNSUPPORTED",
|
|
1395
1535
|
`Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`,
|
|
1396
|
-
{ agentId: agent.id, asset, chainId }
|
|
1536
|
+
{ agentId: agent.id, asset, chainId },
|
|
1537
|
+
agent.id
|
|
1397
1538
|
);
|
|
1398
1539
|
}
|
|
1399
1540
|
}
|
|
@@ -1427,34 +1568,99 @@ var OwneySDK = class {
|
|
|
1427
1568
|
const { asset, amount, agentId } = options;
|
|
1428
1569
|
const state = this.requireState();
|
|
1429
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
|
+
}
|
|
1430
1581
|
if (agentId) {
|
|
1431
1582
|
const agent = this.getAgent(agentId);
|
|
1432
1583
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1433
|
-
return agent.withdraw(state, chainId, amount);
|
|
1584
|
+
return agent.withdraw(state, chainId, token, amount);
|
|
1434
1585
|
}
|
|
1435
1586
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1436
1587
|
if (!amount) {
|
|
1437
1588
|
const results2 = {};
|
|
1438
1589
|
for (const agent of eligibleAgents) {
|
|
1439
|
-
results2[agent.id] = await agent.withdraw(state, chainId);
|
|
1590
|
+
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
1440
1591
|
}
|
|
1441
1592
|
return { agentResult: results2 };
|
|
1442
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);
|
|
1443
1619
|
const results = {};
|
|
1444
|
-
|
|
1445
|
-
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;
|
|
1446
1625
|
try {
|
|
1447
|
-
results[agent.id] = await agent.withdraw(
|
|
1448
|
-
|
|
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
|
+
);
|
|
1449
1632
|
} catch (err) {
|
|
1450
|
-
|
|
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);
|
|
1451
1636
|
}
|
|
1452
1637
|
}
|
|
1453
|
-
if (
|
|
1454
|
-
throw
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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 };
|
|
1458
1664
|
}
|
|
1459
1665
|
// --- Portfolio reads ---
|
|
1460
1666
|
/**
|
|
@@ -1479,7 +1685,10 @@ var OwneySDK = class {
|
|
|
1479
1685
|
})
|
|
1480
1686
|
);
|
|
1481
1687
|
let successCount = 0;
|
|
1482
|
-
|
|
1688
|
+
const agentErrors = {};
|
|
1689
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1690
|
+
const settledResult = balanceResults[i];
|
|
1691
|
+
const [agentId2] = entries[i];
|
|
1483
1692
|
if (settledResult.status === "fulfilled") {
|
|
1484
1693
|
const [id, b] = settledResult.value;
|
|
1485
1694
|
results[id] = b;
|
|
@@ -1487,12 +1696,14 @@ var OwneySDK = class {
|
|
|
1487
1696
|
successCount += 1;
|
|
1488
1697
|
continue;
|
|
1489
1698
|
}
|
|
1490
|
-
|
|
1699
|
+
const reason = settledResult.reason;
|
|
1700
|
+
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
1491
1701
|
}
|
|
1492
1702
|
if (successCount === 0) {
|
|
1493
1703
|
throw new OwneyError(
|
|
1494
1704
|
"BALANCE_ALL_FAILED",
|
|
1495
|
-
"Failed to fetch balances for all active agents."
|
|
1705
|
+
"Failed to fetch balances for all active agents.",
|
|
1706
|
+
{ agentErrors }
|
|
1496
1707
|
);
|
|
1497
1708
|
}
|
|
1498
1709
|
return {
|
|
@@ -1567,16 +1778,24 @@ var OwneySDK = class {
|
|
|
1567
1778
|
results[id] = apy;
|
|
1568
1779
|
}
|
|
1569
1780
|
const balances = {};
|
|
1570
|
-
let totalBalance = 0;
|
|
1571
1781
|
for (const [id, bal] of balanceResults) {
|
|
1572
1782
|
balances[id] = bal;
|
|
1573
|
-
totalBalance += bal;
|
|
1574
1783
|
}
|
|
1575
1784
|
let totalApy = 0;
|
|
1576
|
-
|
|
1785
|
+
let relevantBalance = 0;
|
|
1786
|
+
for (const id of Object.keys(results)) {
|
|
1787
|
+
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
1788
|
+
if (agentApy === 0)
|
|
1789
|
+
continue;
|
|
1790
|
+
relevantBalance += balances[id];
|
|
1791
|
+
}
|
|
1792
|
+
if (relevantBalance > 0) {
|
|
1577
1793
|
for (const id of Object.keys(results)) {
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1794
|
+
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
1795
|
+
if (agentApy === 0)
|
|
1796
|
+
continue;
|
|
1797
|
+
const weight = balances[id] / relevantBalance;
|
|
1798
|
+
totalApy += agentApy * weight;
|
|
1580
1799
|
}
|
|
1581
1800
|
}
|
|
1582
1801
|
return {
|
|
@@ -1719,20 +1938,26 @@ var OwneySDK = class {
|
|
|
1719
1938
|
})
|
|
1720
1939
|
);
|
|
1721
1940
|
let successCount = 0;
|
|
1722
|
-
|
|
1941
|
+
const agentErrors = {};
|
|
1942
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
1943
|
+
const settledResult = balanceResults[i];
|
|
1944
|
+
const [agentId] = entries[i];
|
|
1723
1945
|
if (settledResult.status === "fulfilled") {
|
|
1724
1946
|
const [id, bal] = settledResult.value;
|
|
1725
1947
|
balances[id] = bal;
|
|
1726
1948
|
totalBalance += bal;
|
|
1727
1949
|
successCount += 1;
|
|
1728
1950
|
} else {
|
|
1729
|
-
|
|
1951
|
+
const reason = settledResult.reason;
|
|
1952
|
+
agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
|
|
1953
|
+
console.error(`getAgentAllocation agent "${agentId}" failed:`, reason);
|
|
1730
1954
|
}
|
|
1731
1955
|
}
|
|
1732
1956
|
if (successCount === 0) {
|
|
1733
1957
|
throw new OwneyError(
|
|
1734
1958
|
"ALLOCATION_ALL_FAILED",
|
|
1735
|
-
"Failed to fetch allocation: all agents failed."
|
|
1959
|
+
"Failed to fetch allocation: all agents failed.",
|
|
1960
|
+
{ agentErrors }
|
|
1736
1961
|
);
|
|
1737
1962
|
}
|
|
1738
1963
|
const allocation = {};
|