@owney/sdk 0.7.27-beta.1 → 0.7.27-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1516 -837
- package/dist/index.d.cts +88 -71
- package/dist/index.d.ts +88 -71
- package/dist/index.js +1386 -707
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,213 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var OwneyError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
agentId;
|
|
6
|
+
constructor(code, message, details, agentId) {
|
|
7
|
+
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
8
|
+
super(`${prefix} ${message}`);
|
|
9
|
+
this.name = "OwneyError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
this.agentId = agentId;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var AgentNotFoundError = class extends OwneyError {
|
|
16
|
+
constructor(agentId, available) {
|
|
17
|
+
super(
|
|
18
|
+
"AGENT_NOT_FOUND",
|
|
19
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
20
|
+
{ agentId, available },
|
|
21
|
+
agentId
|
|
22
|
+
);
|
|
23
|
+
this.name = "AgentNotFoundError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var NotConnectedError = class extends OwneyError {
|
|
27
|
+
constructor() {
|
|
28
|
+
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
29
|
+
this.name = "NotConnectedError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var AgentChainIncompatibleError = class extends OwneyError {
|
|
33
|
+
incompatibleAgents;
|
|
34
|
+
connectedChainId;
|
|
35
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
36
|
+
const details = incompatibleAgents.map(
|
|
37
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
38
|
+
).join("; ");
|
|
39
|
+
super(
|
|
40
|
+
"AGENT_CHAIN_INCOMPATIBLE",
|
|
41
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
42
|
+
{ incompatibleAgents, connectedChainId }
|
|
43
|
+
);
|
|
44
|
+
this.name = "AgentChainIncompatibleError";
|
|
45
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
46
|
+
this.connectedChainId = connectedChainId;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/lib/swap/swap.observation.ts
|
|
51
|
+
var active = /* @__PURE__ */ new Set();
|
|
52
|
+
var isOrderObserved = (key2) => active.has(key2);
|
|
53
|
+
async function observeOrder(key2, run) {
|
|
54
|
+
const busy = () => new OwneyError(
|
|
55
|
+
"SWAP_REQUEST_FAILED",
|
|
56
|
+
"This order is already being observed.",
|
|
57
|
+
{ orderHash: key2, watchActive: true, settlementUnconfirmed: true }
|
|
58
|
+
);
|
|
59
|
+
if (active.has(key2)) throw busy();
|
|
60
|
+
active.add(key2);
|
|
61
|
+
try {
|
|
62
|
+
if (typeof navigator !== "undefined" && navigator.locks)
|
|
63
|
+
return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
|
|
64
|
+
if (!lock) throw busy();
|
|
65
|
+
return run();
|
|
66
|
+
});
|
|
67
|
+
return await run();
|
|
68
|
+
} finally {
|
|
69
|
+
active.delete(key2);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/lib/swap/swap.deadline.ts
|
|
74
|
+
async function bounded(work, timeoutMs, error) {
|
|
75
|
+
if (timeoutMs <= 0) throw error();
|
|
76
|
+
let timer;
|
|
77
|
+
try {
|
|
78
|
+
return await Promise.race([
|
|
79
|
+
Promise.resolve().then(work),
|
|
80
|
+
new Promise((_, reject) => {
|
|
81
|
+
timer = setTimeout(() => reject(error()), timeoutMs);
|
|
82
|
+
})
|
|
83
|
+
]);
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function mapConcurrent(items, run, concurrency = 3) {
|
|
89
|
+
const results = new Array(items.length);
|
|
90
|
+
let next = 0;
|
|
91
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
92
|
+
for (; ; ) {
|
|
93
|
+
const index = next++;
|
|
94
|
+
if (index >= items.length) return;
|
|
95
|
+
results[index] = await run(items[index]);
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/lib/permit2-batch.ts
|
|
102
|
+
import { parseAbi, hashStruct } from "viem";
|
|
103
|
+
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
104
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
105
|
+
var ARBITRUM_USDC_ADDRESS = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
|
|
106
|
+
function supportsSponsoredErc2612(chainId, token) {
|
|
107
|
+
const expected = chainId === 8453 ? BASE_USDC_ADDRESS : chainId === 42161 ? ARBITRUM_USDC_ADDRESS : void 0;
|
|
108
|
+
return expected !== void 0 && token.toLowerCase() === expected.toLowerCase();
|
|
109
|
+
}
|
|
110
|
+
var ERC2612_PERMIT_TYPES = {
|
|
111
|
+
Permit: [
|
|
112
|
+
{ name: "owner", type: "address" },
|
|
113
|
+
{ name: "spender", type: "address" },
|
|
114
|
+
{ name: "value", type: "uint256" },
|
|
115
|
+
{ name: "nonce", type: "uint256" },
|
|
116
|
+
{ name: "deadline", type: "uint256" }
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
var ERC2612_READ_ABI = parseAbi([
|
|
120
|
+
"function name() view returns (string)",
|
|
121
|
+
"function version() view returns (string)",
|
|
122
|
+
"function nonces(address owner) view returns (uint256)"
|
|
123
|
+
]);
|
|
124
|
+
function erc2612TypedData(input) {
|
|
125
|
+
return {
|
|
126
|
+
domain: {
|
|
127
|
+
name: input.permit.tokenName,
|
|
128
|
+
version: input.permit.tokenVersion,
|
|
129
|
+
chainId: input.chainId,
|
|
130
|
+
verifyingContract: input.token
|
|
131
|
+
},
|
|
132
|
+
types: ERC2612_PERMIT_TYPES,
|
|
133
|
+
primaryType: "Permit",
|
|
134
|
+
message: {
|
|
135
|
+
owner: input.owner,
|
|
136
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
137
|
+
value: BigInt(input.permit.value),
|
|
138
|
+
nonce: BigInt(input.permit.nonce),
|
|
139
|
+
deadline: BigInt(input.permit.deadline)
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
var PERMIT_BATCH_TYPES = {
|
|
144
|
+
PermitBatchWitnessTransferFrom: [
|
|
145
|
+
{ name: "permitted", type: "TokenPermissions[]" },
|
|
146
|
+
{ name: "spender", type: "address" },
|
|
147
|
+
{ name: "nonce", type: "uint256" },
|
|
148
|
+
{ name: "deadline", type: "uint256" },
|
|
149
|
+
{ name: "witness", type: "Deposit" }
|
|
150
|
+
],
|
|
151
|
+
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
152
|
+
TokenPermissions: [
|
|
153
|
+
{ name: "token", type: "address" },
|
|
154
|
+
{ name: "amount", type: "uint256" }
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
var PERMIT2_BATCH_ABI = parseAbi([
|
|
158
|
+
"struct TokenPermissions { address token; uint256 amount; }",
|
|
159
|
+
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
160
|
+
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
161
|
+
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
162
|
+
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
163
|
+
]);
|
|
164
|
+
function batchPermit(b) {
|
|
165
|
+
return {
|
|
166
|
+
permitted: b.transfers.map((t) => ({
|
|
167
|
+
token: b.token,
|
|
168
|
+
amount: BigInt(t.amount)
|
|
169
|
+
})),
|
|
170
|
+
nonce: BigInt(b.nonce),
|
|
171
|
+
deadline: BigInt(b.deadline)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function batchTypedData(b, spender) {
|
|
175
|
+
return {
|
|
176
|
+
domain: {
|
|
177
|
+
name: "Permit2",
|
|
178
|
+
chainId: b.chainId,
|
|
179
|
+
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
180
|
+
},
|
|
181
|
+
types: PERMIT_BATCH_TYPES,
|
|
182
|
+
primaryType: "PermitBatchWitnessTransferFrom",
|
|
183
|
+
message: {
|
|
184
|
+
...batchPermit(b),
|
|
185
|
+
spender,
|
|
186
|
+
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
191
|
+
function batchWitness(b) {
|
|
192
|
+
return hashStruct({
|
|
193
|
+
types: PERMIT_BATCH_TYPES,
|
|
194
|
+
primaryType: "Deposit",
|
|
195
|
+
data: { recipients: b.transfers.map((t) => t.to) }
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function batchArgs(b) {
|
|
199
|
+
return [
|
|
200
|
+
batchPermit(b),
|
|
201
|
+
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
202
|
+
b.from,
|
|
203
|
+
batchWitness(b),
|
|
204
|
+
BATCH_WITNESS_TYPE,
|
|
205
|
+
b.signature
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
|
|
1
209
|
// src/lib/funding-evidence.ts
|
|
2
|
-
import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
|
|
210
|
+
import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi as parseAbi2 } from "viem";
|
|
3
211
|
import { entryPoint06Abi, entryPoint07Abi, getUserOperationHash, toUserOperation } from "viem/account-abstraction";
|
|
4
212
|
function notifyFunding(callback, evidence) {
|
|
5
213
|
try {
|
|
@@ -8,9 +216,9 @@ function notifyFunding(callback, evidence) {
|
|
|
8
216
|
} catch {
|
|
9
217
|
}
|
|
10
218
|
}
|
|
11
|
-
var fullWithdrawalAbi =
|
|
12
|
-
var withdrawalEventAbi =
|
|
13
|
-
var accountExecutionAbi =
|
|
219
|
+
var fullWithdrawalAbi = parseAbi2(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
220
|
+
var withdrawalEventAbi = parseAbi2(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
221
|
+
var accountExecutionAbi = parseAbi2([
|
|
14
222
|
"function execute(address target,uint256 value,bytes data)",
|
|
15
223
|
"function executeBatch((address target,uint256 value,bytes data)[] calls)"
|
|
16
224
|
]);
|
|
@@ -203,55 +411,6 @@ function registerDepositBatch(callback, transfer) {
|
|
|
203
411
|
batchCallbacks.set(callback, transfer);
|
|
204
412
|
}
|
|
205
413
|
|
|
206
|
-
// src/errors.ts
|
|
207
|
-
var OwneyError = class extends Error {
|
|
208
|
-
code;
|
|
209
|
-
details;
|
|
210
|
-
agentId;
|
|
211
|
-
constructor(code, message, details, agentId) {
|
|
212
|
-
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
213
|
-
super(`${prefix} ${message}`);
|
|
214
|
-
this.name = "OwneyError";
|
|
215
|
-
this.code = code;
|
|
216
|
-
this.details = details;
|
|
217
|
-
this.agentId = agentId;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
var AgentNotFoundError = class extends OwneyError {
|
|
221
|
-
constructor(agentId, available) {
|
|
222
|
-
super(
|
|
223
|
-
"AGENT_NOT_FOUND",
|
|
224
|
-
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
225
|
-
{ agentId, available },
|
|
226
|
-
agentId
|
|
227
|
-
);
|
|
228
|
-
this.name = "AgentNotFoundError";
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
var NotConnectedError = class extends OwneyError {
|
|
232
|
-
constructor() {
|
|
233
|
-
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
234
|
-
this.name = "NotConnectedError";
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
var AgentChainIncompatibleError = class extends OwneyError {
|
|
238
|
-
incompatibleAgents;
|
|
239
|
-
connectedChainId;
|
|
240
|
-
constructor(incompatibleAgents, connectedChainId) {
|
|
241
|
-
const details = incompatibleAgents.map(
|
|
242
|
-
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
243
|
-
).join("; ");
|
|
244
|
-
super(
|
|
245
|
-
"AGENT_CHAIN_INCOMPATIBLE",
|
|
246
|
-
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
247
|
-
{ incompatibleAgents, connectedChainId }
|
|
248
|
-
);
|
|
249
|
-
this.name = "AgentChainIncompatibleError";
|
|
250
|
-
this.incompatibleAgents = incompatibleAgents;
|
|
251
|
-
this.connectedChainId = connectedChainId;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
414
|
// src/lib/transfer-auth.ts
|
|
256
415
|
import { bytesToHex } from "viem";
|
|
257
416
|
|
|
@@ -518,21 +677,21 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
518
677
|
}
|
|
519
678
|
if (typeof value !== "object" || seen.has(value)) return;
|
|
520
679
|
seen.add(value);
|
|
521
|
-
const
|
|
522
|
-
if ([
|
|
680
|
+
const record2 = value;
|
|
681
|
+
if ([record2.status, record2.statusCode, record2.code].some(
|
|
523
682
|
(code) => String(code) === "429"
|
|
524
683
|
)) {
|
|
525
684
|
limited = true;
|
|
526
685
|
}
|
|
527
686
|
for (const key2 of ["retryAfterSeconds", "retryAfter"]) {
|
|
528
|
-
const seconds = Number(
|
|
687
|
+
const seconds = Number(record2[key2]);
|
|
529
688
|
if (Number.isFinite(seconds) && seconds > 0)
|
|
530
689
|
delay = Math.max(delay, seconds * 1e3);
|
|
531
690
|
}
|
|
532
|
-
const retryAt = Number(
|
|
691
|
+
const retryAt = Number(record2.retryAt);
|
|
533
692
|
if (Number.isFinite(retryAt) && retryAt > now)
|
|
534
693
|
delay = Math.max(delay, retryAt - now);
|
|
535
|
-
const headers =
|
|
694
|
+
const headers = record2.headers;
|
|
536
695
|
const header = typeof headers?.get === "function" ? headers.get("Retry-After") : headers?.["retry-after"] ?? headers?.["Retry-After"];
|
|
537
696
|
if (typeof header === "string" || typeof header === "number") {
|
|
538
697
|
const seconds = Number(header);
|
|
@@ -548,10 +707,10 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
548
707
|
"data",
|
|
549
708
|
"fields"
|
|
550
709
|
]) {
|
|
551
|
-
visit(
|
|
710
|
+
visit(record2[key2], depth + 1);
|
|
552
711
|
}
|
|
553
712
|
for (const key2 of ["agentErrors", "failures"]) {
|
|
554
|
-
const entries =
|
|
713
|
+
const entries = record2[key2];
|
|
555
714
|
if (entries && typeof entries === "object") {
|
|
556
715
|
for (const entry of Object.values(entries)) visit(entry, depth + 1);
|
|
557
716
|
}
|
|
@@ -569,7 +728,7 @@ function rateLimitDiagnostics(error) {
|
|
|
569
728
|
if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
|
|
570
729
|
return;
|
|
571
730
|
seen.add(value);
|
|
572
|
-
const
|
|
731
|
+
const record2 = value;
|
|
573
732
|
for (const key2 of [
|
|
574
733
|
"rpcSource",
|
|
575
734
|
"chainId",
|
|
@@ -577,12 +736,12 @@ function rateLimitDiagnostics(error) {
|
|
|
577
736
|
"providerRequestId",
|
|
578
737
|
"statusCode"
|
|
579
738
|
]) {
|
|
580
|
-
const candidate =
|
|
739
|
+
const candidate = record2[key2];
|
|
581
740
|
if (candidate !== void 0 && details[key2] === void 0)
|
|
582
741
|
details = { ...details, [key2]: candidate };
|
|
583
742
|
}
|
|
584
743
|
for (const key2 of ["details", "cause", "response", "fields", "error"])
|
|
585
|
-
visit(
|
|
744
|
+
visit(record2[key2], depth + 1);
|
|
586
745
|
}
|
|
587
746
|
visit(error);
|
|
588
747
|
return {
|
|
@@ -650,12 +809,19 @@ var AgentReads = class {
|
|
|
650
809
|
};
|
|
651
810
|
|
|
652
811
|
// src/agents/zyfai/zyfai.withdraw-submission.ts
|
|
812
|
+
var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
|
|
653
813
|
var listenersByClient = /* @__PURE__ */ new WeakMap();
|
|
654
814
|
function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
655
815
|
const client = sdk.httpClient;
|
|
656
816
|
if (!client || typeof client.get !== "function" || typeof client.post !== "function")
|
|
657
|
-
return {
|
|
658
|
-
|
|
817
|
+
return {
|
|
818
|
+
attempted: () => void 0,
|
|
819
|
+
rejected: () => false,
|
|
820
|
+
request: () => void 0,
|
|
821
|
+
belowMinimum: () => void 0,
|
|
822
|
+
stop: () => {
|
|
823
|
+
}
|
|
824
|
+
};
|
|
659
825
|
let listeners2 = listenersByClient.get(client);
|
|
660
826
|
if (!listeners2) {
|
|
661
827
|
listeners2 = /* @__PURE__ */ new Set();
|
|
@@ -675,6 +841,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
675
841
|
method: method === "get" ? "GET" : "POST",
|
|
676
842
|
path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
|
|
677
843
|
};
|
|
844
|
+
let belowMinimum2;
|
|
678
845
|
const notify2 = (outcome) => {
|
|
679
846
|
if (outcome === "rejected" && matching.length !== 1) return;
|
|
680
847
|
const evidence = matching.length === 1 ? { ...request3 } : {
|
|
@@ -685,7 +852,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
685
852
|
};
|
|
686
853
|
for (const observer of matching) {
|
|
687
854
|
try {
|
|
688
|
-
observer.notify(outcome, evidence);
|
|
855
|
+
observer.notify(outcome, evidence, outcome === "rejected" ? belowMinimum2 : void 0);
|
|
689
856
|
} catch {
|
|
690
857
|
}
|
|
691
858
|
}
|
|
@@ -710,10 +877,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
710
877
|
return await original.apply(this, callArgs);
|
|
711
878
|
} catch (error) {
|
|
712
879
|
const message = error instanceof Error ? error.message : "";
|
|
880
|
+
const minimum = BELOW_MINIMUM.exec(message);
|
|
881
|
+
if (minimum) belowMinimum2 = { symbol: minimum[2], idle: minimum[1], minimum: minimum[3] };
|
|
713
882
|
const status = error?.response?.status;
|
|
714
883
|
if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
715
884
|
request3.httpStatus = status;
|
|
716
|
-
request3.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" :
|
|
885
|
+
request3.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : minimum ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
717
886
|
if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request3.failure) && (request3.httpStatus === void 0 || request3.httpStatus >= 400 && request3.httpStatus < 500)) notify2("rejected");
|
|
718
887
|
throw error;
|
|
719
888
|
} finally {
|
|
@@ -726,32 +895,45 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
726
895
|
let submitted = false;
|
|
727
896
|
let rejected = false;
|
|
728
897
|
let request2 = { attempted: false };
|
|
729
|
-
|
|
898
|
+
let belowMinimum;
|
|
899
|
+
const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
|
|
730
900
|
request2 = evidence;
|
|
731
901
|
if (outcome === "attempted") submitted = true;
|
|
732
|
-
else if (outcome === "rejected")
|
|
902
|
+
else if (outcome === "rejected") {
|
|
903
|
+
rejected = true;
|
|
904
|
+
belowMinimum = detail;
|
|
905
|
+
}
|
|
733
906
|
} };
|
|
734
907
|
listeners2.add(listener);
|
|
735
|
-
return {
|
|
736
|
-
|
|
737
|
-
|
|
908
|
+
return {
|
|
909
|
+
attempted: () => submitted,
|
|
910
|
+
rejected: () => rejected,
|
|
911
|
+
request: () => ({ ...request2 }),
|
|
912
|
+
belowMinimum: () => belowMinimum && { ...belowMinimum },
|
|
913
|
+
stop: () => {
|
|
914
|
+
listeners2.delete(listener);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
738
917
|
}
|
|
739
918
|
|
|
740
919
|
// src/agents/zyfai/zyfai.withdraw-empty.ts
|
|
741
|
-
|
|
742
|
-
|
|
920
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
921
|
+
var symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
|
|
922
|
+
var chain = (value) => {
|
|
923
|
+
if (typeof value === "number") return value;
|
|
924
|
+
if (typeof value !== "string") return void 0;
|
|
925
|
+
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
926
|
+
};
|
|
927
|
+
var zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
928
|
+
var units = (value) => typeof value === "string" && /^(?:0x[0-9a-f]+|\d+)$/i.test(value) ? BigInt(value) : void 0;
|
|
929
|
+
function snapshotPortfolio(raw2) {
|
|
743
930
|
const response = record(raw2);
|
|
744
931
|
const portfolio = record(response?.portfolio);
|
|
745
932
|
const chains = record(portfolio?.portfolioByChain);
|
|
746
|
-
if (response?.success === false || !chains || !Array.isArray(portfolio
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (typeof value === "number") return value;
|
|
751
|
-
if (typeof value !== "string") return void 0;
|
|
752
|
-
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
753
|
-
};
|
|
754
|
-
const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
933
|
+
if (response?.success === false || !portfolio || !chains || !Array.isArray(portfolio.positions) || !Array.isArray(portfolio.staleBalances)) return void 0;
|
|
934
|
+
return { portfolio, chains, entries: [...portfolio.positions, ...portfolio.staleBalances] };
|
|
935
|
+
}
|
|
936
|
+
function chainBalancesMatch(chains, chainId, token, accept) {
|
|
755
937
|
for (const [id, assets] of Object.entries(chains)) {
|
|
756
938
|
const idNumber = chain(id);
|
|
757
939
|
if (idNumber === void 0) return false;
|
|
@@ -761,10 +943,13 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
|
761
943
|
for (const [asset, value] of Object.entries(entries)) {
|
|
762
944
|
if (symbol(asset) !== token) continue;
|
|
763
945
|
const balance = record(value);
|
|
764
|
-
if (!balance || !
|
|
946
|
+
if (!balance || !accept(balance)) return false;
|
|
765
947
|
}
|
|
766
948
|
}
|
|
767
|
-
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
function positionsEmpty(entries, chainId, token) {
|
|
952
|
+
for (const value of entries) {
|
|
768
953
|
const entry = record(value);
|
|
769
954
|
if (!entry) return false;
|
|
770
955
|
const id = chain(entry.chainId ?? entry.chain);
|
|
@@ -776,6 +961,23 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
|
776
961
|
}
|
|
777
962
|
return true;
|
|
778
963
|
}
|
|
964
|
+
function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
965
|
+
const snapshot = snapshotPortfolio(raw2);
|
|
966
|
+
if (!snapshot) return false;
|
|
967
|
+
const pending = snapshot.portfolio.pendingAsyncWithdrawals;
|
|
968
|
+
if (pending !== void 0 && (!Array.isArray(pending) || pending.length > 0)) return false;
|
|
969
|
+
return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => zero(balance.balance)) && positionsEmpty(snapshot.entries, chainId, token);
|
|
970
|
+
}
|
|
971
|
+
function hasIdleOnlyWithdrawalSnapshot(raw2, chainId, token, decimals, idle) {
|
|
972
|
+
const snapshot = snapshotPortfolio(raw2);
|
|
973
|
+
if (!snapshot) return false;
|
|
974
|
+
const pending = snapshot.portfolio.pendingAsyncWithdrawals;
|
|
975
|
+
if (pending !== void 0 && (!Array.isArray(pending) || pending.some((value) => record(value)?.status !== "CLAIMED"))) return false;
|
|
976
|
+
return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => {
|
|
977
|
+
const amount = units(balance.balance);
|
|
978
|
+
return amount !== void 0 && amount <= idle && (balance.decimals === void 0 || balance.decimals === decimals);
|
|
979
|
+
}) && positionsEmpty(snapshot.entries, chainId, token);
|
|
980
|
+
}
|
|
779
981
|
|
|
780
982
|
// src/agents/zyfai/zyfai.deposit.ts
|
|
781
983
|
import { createWalletClient, custom, encodeFunctionData, erc20Abi as erc20Abi2, isAddress } from "viem";
|
|
@@ -828,20 +1030,20 @@ var SupportedAssets = [
|
|
|
828
1030
|
];
|
|
829
1031
|
|
|
830
1032
|
// src/agents/zyfai/zyfai.deposit.ts
|
|
831
|
-
async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
|
|
1033
|
+
async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
|
|
832
1034
|
const owner = state.walletAddress;
|
|
833
1035
|
const provider = state.provider;
|
|
834
|
-
const token = SupportedAssets.find((value) => value.chainId ===
|
|
1036
|
+
const token = SupportedAssets.find((value) => value.chainId === chain2.id && value.symbol === asset);
|
|
835
1037
|
if (!token || !isAddress(owner) || !isAddress(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
|
|
836
|
-
throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId:
|
|
1038
|
+
throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
|
|
837
1039
|
assertCurrent();
|
|
838
|
-
const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId:
|
|
1040
|
+
const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain2.id, amount, asset, strategy: "aggressive" });
|
|
839
1041
|
assertCurrent();
|
|
840
1042
|
const expectedData = encodeFunctionData({ abi: erc20Abi2, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
|
|
841
1043
|
const transfer = prepared?.transfer;
|
|
842
1044
|
if (!transfer || transfer.safeAddress?.toLowerCase() !== smartWallet.toLowerCase() || transfer.tokenAddress?.toLowerCase() !== token.address.toLowerCase() || transfer.to?.toLowerCase() !== token.address.toLowerCase() || transfer.data?.toLowerCase() !== expectedData.toLowerCase() || transfer.value !== "0")
|
|
843
|
-
throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId:
|
|
844
|
-
const wallet = createWalletClient({ account: owner, chain, transport: custom({
|
|
1045
|
+
throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
|
|
1046
|
+
const wallet = createWalletClient({ account: owner, chain: chain2, transport: custom({
|
|
845
1047
|
request: async (args) => {
|
|
846
1048
|
assertCurrent();
|
|
847
1049
|
if (args.method === "eth_sendTransaction") {
|
|
@@ -850,7 +1052,7 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
|
|
|
850
1052
|
provider.request({ method: "eth_chainId" })
|
|
851
1053
|
]);
|
|
852
1054
|
assertCurrent();
|
|
853
|
-
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !==
|
|
1055
|
+
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain2.id)
|
|
854
1056
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, "zyfai");
|
|
855
1057
|
await onBeforeSubmission?.();
|
|
856
1058
|
assertCurrent();
|
|
@@ -858,12 +1060,12 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
|
|
|
858
1060
|
return provider.request(args);
|
|
859
1061
|
}
|
|
860
1062
|
}, { retryCount: 0 }) });
|
|
861
|
-
return wallet.sendTransaction({ account: owner, chain, to: token.address, data: expectedData, value: 0n });
|
|
1063
|
+
return wallet.sendTransaction({ account: owner, chain: chain2, to: token.address, data: expectedData, value: 0n });
|
|
862
1064
|
}
|
|
863
1065
|
|
|
864
1066
|
// src/agents/zyfai/zyfai.agent.ts
|
|
865
1067
|
import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
|
|
866
|
-
import { erc20Abi as erc20Abi3, formatUnits as formatUnits2, isAddress as isAddress2, parseAbi as
|
|
1068
|
+
import { erc20Abi as erc20Abi3, formatUnits as formatUnits2, isAddress as isAddress2, parseAbi as parseAbi3, parseUnits } from "viem";
|
|
867
1069
|
import { base, arbitrum, mainnet } from "viem/chains";
|
|
868
1070
|
|
|
869
1071
|
// src/lib/debug.ts
|
|
@@ -914,8 +1116,8 @@ function assetAliases(asset) {
|
|
|
914
1116
|
const normalised = asset.toLowerCase();
|
|
915
1117
|
return normalised === "weth" ? ["weth", "eth"] : [normalised];
|
|
916
1118
|
}
|
|
917
|
-
function matchesAsset(
|
|
918
|
-
return assetAliases(asset).includes(
|
|
1119
|
+
function matchesAsset(symbol2, asset) {
|
|
1120
|
+
return assetAliases(asset).includes(symbol2.toLowerCase());
|
|
919
1121
|
}
|
|
920
1122
|
|
|
921
1123
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
@@ -929,10 +1131,10 @@ var DECIMALS_BY_SYMBOL = {
|
|
|
929
1131
|
wbtc: 8,
|
|
930
1132
|
cbbtc: 8
|
|
931
1133
|
};
|
|
932
|
-
function tokenDecimals(
|
|
1134
|
+
function tokenDecimals(symbol2, explicit) {
|
|
933
1135
|
if (typeof explicit === "number" && Number.isFinite(explicit))
|
|
934
1136
|
return explicit;
|
|
935
|
-
return DECIMALS_BY_SYMBOL[(
|
|
1137
|
+
return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
|
|
936
1138
|
}
|
|
937
1139
|
function mapWithdraw(raw2) {
|
|
938
1140
|
return {
|
|
@@ -952,10 +1154,10 @@ var NAME_TO_CHAIN_ID = Object.entries(
|
|
|
952
1154
|
acc[name.toLowerCase()] = Number(id);
|
|
953
1155
|
return acc;
|
|
954
1156
|
}, {});
|
|
955
|
-
function resolveChainId(
|
|
956
|
-
const asNum = Number(
|
|
1157
|
+
function resolveChainId(chain2) {
|
|
1158
|
+
const asNum = Number(chain2);
|
|
957
1159
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
958
|
-
return NAME_TO_CHAIN_ID[
|
|
1160
|
+
return NAME_TO_CHAIN_ID[chain2.trim().toLowerCase()] ?? NaN;
|
|
959
1161
|
}
|
|
960
1162
|
function mapPendingAllocations(raw2) {
|
|
961
1163
|
if (!Array.isArray(raw2)) return void 0;
|
|
@@ -995,16 +1197,16 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
995
1197
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
996
1198
|
const positionChainId = Number(chainIdKey);
|
|
997
1199
|
for (const [
|
|
998
|
-
|
|
1200
|
+
symbol2,
|
|
999
1201
|
{ balance, balanceWithFee, decimals }
|
|
1000
1202
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
1001
1203
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
1002
|
-
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(
|
|
1204
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol2, decimals));
|
|
1003
1205
|
totalBalance += Number(amount);
|
|
1004
1206
|
tokens.push({
|
|
1005
1207
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
1006
1208
|
chainId: positionChainId,
|
|
1007
|
-
asset:
|
|
1209
|
+
asset: symbol2,
|
|
1008
1210
|
amount
|
|
1009
1211
|
});
|
|
1010
1212
|
}
|
|
@@ -1072,12 +1274,12 @@ function mapEarnings(raw2, smartWallet) {
|
|
|
1072
1274
|
totalEarningsByChain
|
|
1073
1275
|
)) {
|
|
1074
1276
|
const positionChainId = Number(chainIdKey);
|
|
1075
|
-
for (const [
|
|
1277
|
+
for (const [symbol2, earnings] of Object.entries(tokensBySymbol ?? {})) {
|
|
1076
1278
|
const amount = Number(earnings);
|
|
1077
1279
|
tokens.push({
|
|
1078
1280
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
1079
1281
|
chainId: positionChainId,
|
|
1080
|
-
asset:
|
|
1282
|
+
asset: symbol2,
|
|
1081
1283
|
amount: String(amount)
|
|
1082
1284
|
});
|
|
1083
1285
|
}
|
|
@@ -1097,8 +1299,8 @@ function mapWeightedApyByChain(raw2) {
|
|
|
1097
1299
|
const chainId = Number(chainKey);
|
|
1098
1300
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
1099
1301
|
const perAsset = {};
|
|
1100
|
-
for (const [
|
|
1101
|
-
const token =
|
|
1302
|
+
for (const [symbol2, apy] of Object.entries(tokenApy)) {
|
|
1303
|
+
const token = symbol2;
|
|
1102
1304
|
if (!SUPPORTED_TOKENS.includes(token)) continue;
|
|
1103
1305
|
perAsset[token] = apy;
|
|
1104
1306
|
}
|
|
@@ -1329,8 +1531,8 @@ function computeAllocationApy(positions) {
|
|
|
1329
1531
|
function earningsForToken(bucket, chainId, asset) {
|
|
1330
1532
|
const tokens = bucket?.[String(chainId)];
|
|
1331
1533
|
if (!tokens) return null;
|
|
1332
|
-
for (const [
|
|
1333
|
-
if (!matchesAsset(
|
|
1534
|
+
for (const [symbol2, value] of Object.entries(tokens)) {
|
|
1535
|
+
if (!matchesAsset(symbol2, asset)) continue;
|
|
1334
1536
|
const amount = Number(value);
|
|
1335
1537
|
return Number.isFinite(amount) ? amount : null;
|
|
1336
1538
|
}
|
|
@@ -1347,8 +1549,8 @@ function assetsInSnapshot(entry, chainId) {
|
|
|
1347
1549
|
entry.current_earnings_by_token,
|
|
1348
1550
|
entry.total_earnings_by_token
|
|
1349
1551
|
]) {
|
|
1350
|
-
for (const
|
|
1351
|
-
seen.add(
|
|
1552
|
+
for (const symbol2 of Object.keys(bucket?.[key2] ?? {})) {
|
|
1553
|
+
seen.add(symbol2.toUpperCase());
|
|
1352
1554
|
}
|
|
1353
1555
|
}
|
|
1354
1556
|
return [...seen];
|
|
@@ -1717,13 +1919,13 @@ function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
|
|
|
1717
1919
|
42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
|
|
1718
1920
|
};
|
|
1719
1921
|
}
|
|
1720
|
-
function createPaidRpcClient(
|
|
1922
|
+
function createPaidRpcClient(chain2, rpcUrls) {
|
|
1721
1923
|
const url = resolveRpcUrl(
|
|
1722
1924
|
rpcUrls,
|
|
1723
|
-
|
|
1925
|
+
chain2.id
|
|
1724
1926
|
);
|
|
1725
1927
|
return createPublicClient({
|
|
1726
|
-
chain,
|
|
1928
|
+
chain: chain2,
|
|
1727
1929
|
transport: http(url, {
|
|
1728
1930
|
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1729
1931
|
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
@@ -1738,8 +1940,8 @@ function headerValue(error, name) {
|
|
|
1738
1940
|
return;
|
|
1739
1941
|
if (seen.has(candidate)) return;
|
|
1740
1942
|
seen.add(candidate);
|
|
1741
|
-
const
|
|
1742
|
-
const headers =
|
|
1943
|
+
const record2 = candidate;
|
|
1944
|
+
const headers = record2.headers;
|
|
1743
1945
|
let found;
|
|
1744
1946
|
if (typeof headers?.get === "function") {
|
|
1745
1947
|
found = headers.get(name);
|
|
@@ -1752,7 +1954,7 @@ function headerValue(error, name) {
|
|
|
1752
1954
|
return;
|
|
1753
1955
|
}
|
|
1754
1956
|
for (const key2 of ["cause", "details", "response", "error"])
|
|
1755
|
-
visit(
|
|
1957
|
+
visit(record2[key2], depth + 1);
|
|
1756
1958
|
}
|
|
1757
1959
|
visit(error);
|
|
1758
1960
|
return value;
|
|
@@ -1765,16 +1967,16 @@ function statusCode(error) {
|
|
|
1765
1967
|
return;
|
|
1766
1968
|
if (seen.has(candidate)) return;
|
|
1767
1969
|
seen.add(candidate);
|
|
1768
|
-
const
|
|
1970
|
+
const record2 = candidate;
|
|
1769
1971
|
for (const key2 of ["status", "statusCode"]) {
|
|
1770
|
-
const parsed = Number(
|
|
1972
|
+
const parsed = Number(record2[key2]);
|
|
1771
1973
|
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1772
1974
|
status = parsed;
|
|
1773
1975
|
return;
|
|
1774
1976
|
}
|
|
1775
1977
|
}
|
|
1776
1978
|
for (const key2 of ["cause", "details", "response", "error"])
|
|
1777
|
-
visit(
|
|
1979
|
+
visit(record2[key2], depth + 1);
|
|
1778
1980
|
}
|
|
1779
1981
|
visit(error);
|
|
1780
1982
|
return status;
|
|
@@ -1821,7 +2023,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
|
1821
2023
|
}
|
|
1822
2024
|
|
|
1823
2025
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1824
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI =
|
|
2026
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi3([
|
|
1825
2027
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1826
2028
|
]);
|
|
1827
2029
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
@@ -1834,6 +2036,17 @@ var SPLITTING_ASSETS = ["USDC", "WETH"];
|
|
|
1834
2036
|
var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
|
|
1835
2037
|
var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1836
2038
|
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
2039
|
+
function belowMinimumCeiling(rejection, token, decimals) {
|
|
2040
|
+
if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
|
|
2041
|
+
const ceiling = (display) => {
|
|
2042
|
+
const places = display.split(".")[1]?.length ?? 0;
|
|
2043
|
+
if (places >= decimals) return void 0;
|
|
2044
|
+
return parseUnits(display, decimals) + 5n * 10n ** BigInt(decimals - places - 1);
|
|
2045
|
+
};
|
|
2046
|
+
const idle = ceiling(rejection.idle);
|
|
2047
|
+
const minimum = ceiling(rejection.minimum);
|
|
2048
|
+
return idle === void 0 || minimum === void 0 ? void 0 : idle < minimum ? idle : minimum;
|
|
2049
|
+
}
|
|
1837
2050
|
var ZyfaiAgent = class _ZyfaiAgent {
|
|
1838
2051
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1839
2052
|
this.apiKey = apiKey;
|
|
@@ -2790,8 +3003,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2790
3003
|
// --- IAgent: Fund operations ---
|
|
2791
3004
|
/** Only after a definitive endpoint rejection, combine a new provider
|
|
2792
3005
|
* snapshot with the Safe's exact idle balance. Never parse its rounded error
|
|
2793
|
-
* amount as proof of zero, or use this to skip sending a new MAX request.
|
|
2794
|
-
|
|
3006
|
+
* amount as proof of zero, or use this to skip sending a new MAX request.
|
|
3007
|
+
* After Zyfai's minimum-withdrawal rejection, exact idle-only dust that fits
|
|
3008
|
+
* within the message's rounded amounts is reported as not withdrawable. */
|
|
3009
|
+
async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner, belowMinimum) {
|
|
2795
3010
|
const internals = this.sdk;
|
|
2796
3011
|
const generation = this.earningsGeneration;
|
|
2797
3012
|
let expired = false;
|
|
@@ -2822,22 +3037,31 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2822
3037
|
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2823
3038
|
this.id
|
|
2824
3039
|
);
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3040
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
3041
|
+
if (!asset || portfolio === void 0) return;
|
|
3042
|
+
const empty = hasEmptyWithdrawalSnapshot(portfolio, chainId, token);
|
|
3043
|
+
const ceiling = belowMinimum && belowMinimumCeiling(belowMinimum, token, asset.decimals);
|
|
3044
|
+
if (!empty && ceiling === void 0) return;
|
|
3045
|
+
const idle = await this.getPublicClient(chainId).readContract({
|
|
3046
|
+
address: asset.address,
|
|
3047
|
+
abi: erc20Abi3,
|
|
3048
|
+
functionName: "balanceOf",
|
|
3049
|
+
args: [wallet.address]
|
|
3050
|
+
}).catch(() => void 0);
|
|
3051
|
+
assertScope();
|
|
3052
|
+
if (idle === 0n && empty) throw new OwneyError(
|
|
3053
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3054
|
+
"No funds remain in Zyfai for this asset.",
|
|
3055
|
+
{ available: "0", chainId, token },
|
|
3056
|
+
this.id
|
|
3057
|
+
);
|
|
3058
|
+
if (typeof idle === "bigint" && idle > 0n && ceiling !== void 0 && idle < ceiling && hasIdleOnlyWithdrawalSnapshot(portfolio, chainId, token, asset.decimals, idle))
|
|
3059
|
+
throw new OwneyError(
|
|
2835
3060
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2836
|
-
"
|
|
2837
|
-
{ available: "0", chainId, token },
|
|
3061
|
+
"The remaining Zyfai balance is below Zyfai's minimum withdrawal.",
|
|
3062
|
+
{ available: "0", chainId, token, reason: "ZYFAI_BELOW_MINIMUM_WITHDRAWAL", remainingRaw: idle.toString() },
|
|
2838
3063
|
this.id
|
|
2839
3064
|
);
|
|
2840
|
-
}
|
|
2841
3065
|
};
|
|
2842
3066
|
let timer;
|
|
2843
3067
|
try {
|
|
@@ -2888,7 +3112,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2888
3112
|
throw unavailable();
|
|
2889
3113
|
}
|
|
2890
3114
|
}
|
|
2891
|
-
async withdraw(state, chainId, token, amount, onProgress) {
|
|
3115
|
+
async withdraw(state, chainId, token, amount, onProgress, _gasMode, _onBeforeSubmission, assertOperation) {
|
|
2892
3116
|
if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
|
|
2893
3117
|
throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
|
|
2894
3118
|
const validChainId = isValidChainId(chainId);
|
|
@@ -2924,7 +3148,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2924
3148
|
throw staleSessionError;
|
|
2925
3149
|
}
|
|
2926
3150
|
};
|
|
2927
|
-
|
|
3151
|
+
const assertSubmission = () => {
|
|
3152
|
+
assertCurrent();
|
|
3153
|
+
assertOperation?.();
|
|
3154
|
+
};
|
|
3155
|
+
let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
|
|
2928
3156
|
const authToken = internals.httpClient?.authToken;
|
|
2929
3157
|
let raw2;
|
|
2930
3158
|
try {
|
|
@@ -2943,7 +3171,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2943
3171
|
}
|
|
2944
3172
|
if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
|
|
2945
3173
|
try {
|
|
2946
|
-
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
|
|
3174
|
+
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner, observation.belowMinimum());
|
|
2947
3175
|
} catch (proofError) {
|
|
2948
3176
|
if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
|
|
2949
3177
|
}
|
|
@@ -2956,9 +3184,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2956
3184
|
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2957
3185
|
this.id
|
|
2958
3186
|
);
|
|
3187
|
+
assertSubmission();
|
|
2959
3188
|
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
2960
3189
|
observation.stop();
|
|
2961
|
-
observation = observeWithdrawalSubmission(executionSdk, validChainId, token,
|
|
3190
|
+
observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
|
|
2962
3191
|
}
|
|
2963
3192
|
}
|
|
2964
3193
|
if (!raw2) throw new OwneyError(
|
|
@@ -3352,7 +3581,7 @@ import {
|
|
|
3352
3581
|
erc4626Abi as erc4626Abi2,
|
|
3353
3582
|
getAddress as getAddress5,
|
|
3354
3583
|
isAddress as isAddress4,
|
|
3355
|
-
parseUnits
|
|
3584
|
+
parseUnits as parseUnits2
|
|
3356
3585
|
} from "viem";
|
|
3357
3586
|
import { base as base4 } from "viem/chains";
|
|
3358
3587
|
|
|
@@ -3503,6 +3732,11 @@ var YieldseekerAuth = class {
|
|
|
3503
3732
|
key(state, chainId) {
|
|
3504
3733
|
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
3505
3734
|
}
|
|
3735
|
+
/** Observation must never open a sign-in prompt. */
|
|
3736
|
+
peekToken(state, chainId) {
|
|
3737
|
+
const token = this.tokens.get(this.key(state, chainId)) ?? readYieldseekerSession(state.walletAddress, chainId);
|
|
3738
|
+
return token && this.isReusable(token, state, chainId) ? token : void 0;
|
|
3739
|
+
}
|
|
3506
3740
|
async getToken(state, chainId) {
|
|
3507
3741
|
const key2 = this.key(state, chainId);
|
|
3508
3742
|
const scope = { address: state.walletAddress, chainId };
|
|
@@ -3687,10 +3921,10 @@ var YieldseekerApiError = class extends Error {
|
|
|
3687
3921
|
};
|
|
3688
3922
|
function providerError(body, fallback2) {
|
|
3689
3923
|
if (!body || typeof body !== "object") return { code: fallback2 };
|
|
3690
|
-
const
|
|
3924
|
+
const record2 = body;
|
|
3691
3925
|
return {
|
|
3692
|
-
code: typeof
|
|
3693
|
-
fields:
|
|
3926
|
+
code: typeof record2.message === "string" ? record2.message : fallback2,
|
|
3927
|
+
fields: record2.fields && typeof record2.fields === "object" ? record2.fields : void 0
|
|
3694
3928
|
};
|
|
3695
3929
|
}
|
|
3696
3930
|
var YieldseekerApiClient = class {
|
|
@@ -3844,16 +4078,16 @@ function riskAdjustedApyForDays(option, days) {
|
|
|
3844
4078
|
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
3845
4079
|
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
3846
4080
|
}
|
|
3847
|
-
function assetAddressValue(
|
|
3848
|
-
const entry = Object.entries(
|
|
4081
|
+
function assetAddressValue(record2, address) {
|
|
4082
|
+
const entry = Object.entries(record2).find(
|
|
3849
4083
|
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
3850
4084
|
);
|
|
3851
4085
|
return entry?.[1] ?? "0";
|
|
3852
4086
|
}
|
|
3853
4087
|
function position(value, asset, baseAssetDecimals) {
|
|
3854
|
-
const option = value?.
|
|
4088
|
+
const option = value?.vaultOption;
|
|
3855
4089
|
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress3(option.address)) {
|
|
3856
|
-
return invalid("
|
|
4090
|
+
return invalid("vault positions", "missing vault metadata");
|
|
3857
4091
|
}
|
|
3858
4092
|
return {
|
|
3859
4093
|
chain: "BASE",
|
|
@@ -3869,10 +4103,10 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
3869
4103
|
amount: balanceDecimal(
|
|
3870
4104
|
value.assetsBase,
|
|
3871
4105
|
baseAssetDecimals,
|
|
3872
|
-
"
|
|
4106
|
+
"vault positions"
|
|
3873
4107
|
),
|
|
3874
|
-
amountRaw: balanceRaw(value.assetsRaw, "
|
|
3875
|
-
withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "
|
|
4108
|
+
amountRaw: balanceRaw(value.assetsRaw, "vault positions").toString(),
|
|
4109
|
+
withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "vault positions").toString(),
|
|
3876
4110
|
apy: percent(option.riskAdjustedApy),
|
|
3877
4111
|
tvl: Number(option.totalDepositsUsd),
|
|
3878
4112
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -4091,52 +4325,82 @@ function actionEntry(action) {
|
|
|
4091
4325
|
rebalanceLog: []
|
|
4092
4326
|
};
|
|
4093
4327
|
}
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
const
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
if (
|
|
4103
|
-
|
|
4104
|
-
const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
4105
|
-
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
4328
|
+
var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
|
|
4329
|
+
function allocationLegs(value, context, requireAmount) {
|
|
4330
|
+
if (!Array.isArray(value)) return void 0;
|
|
4331
|
+
const legs = [];
|
|
4332
|
+
for (const item of value) {
|
|
4333
|
+
if (!item || typeof item !== "object") return void 0;
|
|
4334
|
+
const leg = item;
|
|
4335
|
+
const symbol2 = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
4336
|
+
if ((requireAmount || symbol2) && symbol2 !== context.asset && !(context.asset === "WETH" && symbol2 === "ETH"))
|
|
4337
|
+
return void 0;
|
|
4106
4338
|
if (typeof leg.vaultAddress !== "string" || !isAddress3(leg.vaultAddress)) return void 0;
|
|
4107
4339
|
const rawAmount2 = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
|
|
4108
|
-
if (rawAmount2 === void 0 || rawAmount2 <= 0n) return void 0;
|
|
4109
|
-
const
|
|
4110
|
-
positions.
|
|
4340
|
+
if (requireAmount && (rawAmount2 === void 0 || rawAmount2 <= 0n)) return void 0;
|
|
4341
|
+
const vault = leg.vaultAddress.toLowerCase();
|
|
4342
|
+
const option = context.positions.find((position2) => position2.vaultOption.address.toLowerCase() === vault)?.vaultOption;
|
|
4343
|
+
const receipt = context.historic?.assets?.find((asset) => asset.address.toLowerCase() === vault && !asset.isSpam);
|
|
4344
|
+
legs.push({
|
|
4111
4345
|
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
4112
|
-
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
4113
|
-
|
|
4114
|
-
amount: formatUnits3(rawAmount2, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
4346
|
+
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name ?? (receipt?.name || receipt?.symbol || void 0),
|
|
4347
|
+
rawAmount: rawAmount2
|
|
4115
4348
|
});
|
|
4116
|
-
amountRaw += rawAmount2;
|
|
4117
4349
|
}
|
|
4350
|
+
return legs;
|
|
4351
|
+
}
|
|
4352
|
+
function allocationEntry(action, context) {
|
|
4353
|
+
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
4354
|
+
const hash2 = action.details?.depositTransactionHash;
|
|
4355
|
+
const withdrawHash = action.details?.withdrawTransactionHash;
|
|
4356
|
+
if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
|
|
4357
|
+
const deposits = allocationLegs(action.details?.deposits, context, true);
|
|
4358
|
+
const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
|
|
4359
|
+
if (!deposits?.length || !withdrawals) return null;
|
|
4360
|
+
const decimals = YIELDSEEKER_ASSET_METADATA[context.asset].decimals;
|
|
4361
|
+
const positions = deposits.map((leg) => ({
|
|
4362
|
+
protocol: leg.protocol,
|
|
4363
|
+
pool: leg.pool,
|
|
4364
|
+
tokenSymbol: context.asset,
|
|
4365
|
+
amount: formatUnits3(leg.rawAmount, decimals)
|
|
4366
|
+
}));
|
|
4367
|
+
const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
|
|
4368
|
+
const rebalance = withdrawals.length > 0;
|
|
4369
|
+
const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
|
|
4370
|
+
const source = withdrawals.length === 1 ? withdrawals[0] : {
|
|
4371
|
+
protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
|
|
4372
|
+
pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
|
|
4373
|
+
};
|
|
4118
4374
|
return {
|
|
4119
4375
|
agent: "yieldseeker",
|
|
4120
|
-
action: "Deposit",
|
|
4376
|
+
action: rebalance ? "Rebalance" : "Deposit",
|
|
4121
4377
|
date: action.createdDate,
|
|
4122
4378
|
oldApy: null,
|
|
4123
4379
|
newApy: null,
|
|
4124
4380
|
positions,
|
|
4125
|
-
rebalanceLog:
|
|
4381
|
+
rebalanceLog: rebalance ? deposits.map((leg) => ({
|
|
4382
|
+
fromProtocol: source.protocol,
|
|
4383
|
+
...source.pool ? { fromPool: source.pool } : {},
|
|
4384
|
+
toProtocol: leg.protocol,
|
|
4385
|
+
...leg.pool ? { toPool: leg.pool } : {},
|
|
4386
|
+
tokenSymbol: context.asset,
|
|
4387
|
+
amount: formatUnits3(leg.rawAmount, decimals),
|
|
4388
|
+
status: "success"
|
|
4389
|
+
})) : [],
|
|
4126
4390
|
transactions: [{
|
|
4127
|
-
txHashes:
|
|
4391
|
+
txHashes: hashes,
|
|
4128
4392
|
chainId: context.agent.chainId,
|
|
4129
4393
|
tokenAddress: context.agent.assetAddress,
|
|
4130
4394
|
tokenSymbol: context.asset,
|
|
4131
4395
|
agentWallet: context.wallet.walletAddress,
|
|
4132
4396
|
amountRaw: String(amountRaw),
|
|
4133
|
-
amount: formatUnits3(amountRaw,
|
|
4397
|
+
amount: formatUnits3(amountRaw, decimals)
|
|
4134
4398
|
}]
|
|
4135
4399
|
};
|
|
4136
4400
|
}
|
|
4137
4401
|
function depositDestination(context, movement) {
|
|
4138
4402
|
const to = movement.toAddress.toLowerCase();
|
|
4139
|
-
const option = context.positions.find(({
|
|
4403
|
+
const option = context.positions.find(({ vaultOption }) => vaultOption.chainId === movement.chainId && vaultOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && vaultOption.address.toLowerCase() === to)?.vaultOption;
|
|
4140
4404
|
if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
|
|
4141
4405
|
const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
|
|
4142
4406
|
const minted = receipt && context.historic?.movements.some((candidate) => candidate.chainId === movement.chainId && candidate.transactionHash.toLowerCase() === movement.transactionHash.toLowerCase() && candidate.assetAddress.toLowerCase() === receipt.address.toLowerCase() && candidate.fromAddress.toLowerCase() === "0x0000000000000000000000000000000000000000" && candidate.toAddress.toLowerCase() === context.wallet.walletAddress.toLowerCase() && raw(candidate.assetAmount, "historic position") > 0n);
|
|
@@ -4201,6 +4465,23 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
4201
4465
|
seenMovements.add(key2);
|
|
4202
4466
|
return true;
|
|
4203
4467
|
});
|
|
4468
|
+
const allocations = /* @__PURE__ */ new Map();
|
|
4469
|
+
const rebalanceHashes = /* @__PURE__ */ new Set();
|
|
4470
|
+
const actions = [];
|
|
4471
|
+
for (const action of context.actions ?? []) {
|
|
4472
|
+
const allocation = allocationEntry(action, context);
|
|
4473
|
+
if (allocation === null) continue;
|
|
4474
|
+
if (!allocation) {
|
|
4475
|
+
const entry = actionEntry(action);
|
|
4476
|
+
if (entry) actions.push(entry);
|
|
4477
|
+
continue;
|
|
4478
|
+
}
|
|
4479
|
+
const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
4480
|
+
if (allocations.has(hash2)) continue;
|
|
4481
|
+
allocations.set(hash2, allocation);
|
|
4482
|
+
if (allocation.action === "Rebalance")
|
|
4483
|
+
for (const value of allocation.transactions[0].txHashes) rebalanceHashes.add(value.toLowerCase());
|
|
4484
|
+
}
|
|
4204
4485
|
const movementEntries = movements.map(
|
|
4205
4486
|
(movement) => movementEntry(
|
|
4206
4487
|
movement,
|
|
@@ -4211,19 +4492,10 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
4211
4492
|
options.vaultAddresses,
|
|
4212
4493
|
depositDestination(context, movement)
|
|
4213
4494
|
)
|
|
4214
|
-
).filter((entry) => entry !== void 0);
|
|
4495
|
+
).filter((entry) => entry !== void 0 && !((entry.action === "Deposit" || entry.action === "Top up") && rebalanceHashes.has(entry.transactions[0]?.txHashes[0]?.toLowerCase() ?? "")));
|
|
4215
4496
|
const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
if (allocation) {
|
|
4219
|
-
const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
4220
|
-
if (deposits.has(hash2)) return [];
|
|
4221
|
-
deposits.add(hash2);
|
|
4222
|
-
return [allocation];
|
|
4223
|
-
}
|
|
4224
|
-
const entry = actionEntry(action);
|
|
4225
|
-
return entry ? [entry] : [];
|
|
4226
|
-
});
|
|
4497
|
+
for (const [hash2, allocation] of allocations)
|
|
4498
|
+
if (allocation.action === "Rebalance" || !deposits.has(hash2)) actions.push(allocation);
|
|
4227
4499
|
return [...movementEntries, ...actions];
|
|
4228
4500
|
});
|
|
4229
4501
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -4301,8 +4573,8 @@ function mapYieldseekerProfile(address, contexts) {
|
|
|
4301
4573
|
const protocols = /* @__PURE__ */ new Set();
|
|
4302
4574
|
for (const context of contexts) {
|
|
4303
4575
|
for (const current of context.positions) {
|
|
4304
|
-
if (current.
|
|
4305
|
-
protocols.add(String(current.
|
|
4576
|
+
if (current.vaultOption?.provider) {
|
|
4577
|
+
protocols.add(String(current.vaultOption.provider));
|
|
4306
4578
|
}
|
|
4307
4579
|
}
|
|
4308
4580
|
}
|
|
@@ -4320,7 +4592,7 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
4320
4592
|
const perAsset = {};
|
|
4321
4593
|
const all = [];
|
|
4322
4594
|
for (const entry of options) {
|
|
4323
|
-
const apys = entry.
|
|
4595
|
+
const apys = entry.vaultOptions.map(
|
|
4324
4596
|
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
4325
4597
|
).filter(Number.isFinite);
|
|
4326
4598
|
if (apys.length === 0) continue;
|
|
@@ -4384,13 +4656,13 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
|
|
|
4384
4656
|
import {
|
|
4385
4657
|
getAddress as getAddress3,
|
|
4386
4658
|
isAddressEqual,
|
|
4387
|
-
parseAbi as
|
|
4659
|
+
parseAbi as parseAbi4
|
|
4388
4660
|
} from "viem";
|
|
4389
4661
|
var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
|
|
4390
4662
|
var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
|
|
4391
4663
|
var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
|
|
4392
4664
|
var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
4393
|
-
var walletAbi =
|
|
4665
|
+
var walletAbi = parseAbi4([
|
|
4394
4666
|
"function owner() view returns (address)",
|
|
4395
4667
|
"function ownerAgentIndex() view returns (uint256)",
|
|
4396
4668
|
"function baseAsset() view returns (address)",
|
|
@@ -4398,7 +4670,7 @@ var walletAbi = parseAbi3([
|
|
|
4398
4670
|
"function upgradeToLatest()",
|
|
4399
4671
|
"event Upgraded(address indexed implementation)"
|
|
4400
4672
|
]);
|
|
4401
|
-
var factoryAbi =
|
|
4673
|
+
var factoryAbi = parseAbi4([
|
|
4402
4674
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
4403
4675
|
"function agentWalletImplementation() view returns (address)"
|
|
4404
4676
|
]);
|
|
@@ -4548,11 +4820,11 @@ function isHash(value) {
|
|
|
4548
4820
|
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
4549
4821
|
}
|
|
4550
4822
|
async function assertOwnerSession(state, owner) {
|
|
4551
|
-
const [
|
|
4823
|
+
const [chain2, accounts] = await Promise.all([
|
|
4552
4824
|
state.provider.request({ method: "eth_chainId" }),
|
|
4553
4825
|
state.provider.request({ method: "eth_accounts" })
|
|
4554
4826
|
]);
|
|
4555
|
-
if (Number(
|
|
4827
|
+
if (Number(chain2) !== 8453)
|
|
4556
4828
|
throw new OwneyError(
|
|
4557
4829
|
"CHAIN_MISMATCH",
|
|
4558
4830
|
"Switch back to Base before withdrawing.",
|
|
@@ -4803,6 +5075,7 @@ async function submitEoa(input) {
|
|
|
4803
5075
|
chain: base3,
|
|
4804
5076
|
transport: custom3(input.state.provider)
|
|
4805
5077
|
});
|
|
5078
|
+
input.assertCurrent?.();
|
|
4806
5079
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
4807
5080
|
operation = { ...operation, signature };
|
|
4808
5081
|
await assertOwnerSession(input.state, input.owner);
|
|
@@ -4820,7 +5093,9 @@ async function submitEoa(input) {
|
|
|
4820
5093
|
paymasterAndData: operation.paymasterAndData
|
|
4821
5094
|
};
|
|
4822
5095
|
savePending(input.onPending, { userOpHash, userOperation }, true);
|
|
5096
|
+
input.assertCurrent?.();
|
|
4823
5097
|
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
5098
|
+
input.assertCurrent?.();
|
|
4824
5099
|
const submittedHash = await rpc(
|
|
4825
5100
|
input.rpcUrl,
|
|
4826
5101
|
"eth_sendUserOperation",
|
|
@@ -4887,7 +5162,9 @@ async function submitSmart(input) {
|
|
|
4887
5162
|
assertIntentFresh(input.expiresAt);
|
|
4888
5163
|
const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4889
5164
|
savePending(input.onPending, { callsId: requestedId }, true);
|
|
5165
|
+
input.assertCurrent?.();
|
|
4890
5166
|
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
5167
|
+
input.assertCurrent?.();
|
|
4891
5168
|
try {
|
|
4892
5169
|
sent = await input.state.provider.request({
|
|
4893
5170
|
method: "wallet_sendCalls",
|
|
@@ -5014,7 +5291,7 @@ var OWNEY_AGENT_RULE_PRESET = "explorative";
|
|
|
5014
5291
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
5015
5292
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
5016
5293
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
5017
|
-
var
|
|
5294
|
+
var YIELDSEEKER_VAULT_OPTIONS_CACHE_MS = 6e4;
|
|
5018
5295
|
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
5019
5296
|
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
5020
5297
|
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
@@ -5117,6 +5394,7 @@ var YieldseekerAgent = class {
|
|
|
5117
5394
|
pendingReads = /* @__PURE__ */ new Map();
|
|
5118
5395
|
readGeneration = 0;
|
|
5119
5396
|
portfolioVersions = /* @__PURE__ */ new Map();
|
|
5397
|
+
liquidityVersions = /* @__PURE__ */ new Map();
|
|
5120
5398
|
snapshotFailures = /* @__PURE__ */ new Map();
|
|
5121
5399
|
standardReadFailures = /* @__PURE__ */ new Map();
|
|
5122
5400
|
reconcileUntil = /* @__PURE__ */ new Map();
|
|
@@ -5126,8 +5404,8 @@ var YieldseekerAgent = class {
|
|
|
5126
5404
|
// settlement; keep slow reconciliation after the fast window expires.
|
|
5127
5405
|
snapshotMovements = /* @__PURE__ */ new Map();
|
|
5128
5406
|
movementEpochs = /* @__PURE__ */ new Map();
|
|
5129
|
-
|
|
5130
|
-
|
|
5407
|
+
vaultOptions = /* @__PURE__ */ new Map();
|
|
5408
|
+
pendingVaultOptions = /* @__PURE__ */ new Map();
|
|
5131
5409
|
constructor(owneyApiKey, options = {}) {
|
|
5132
5410
|
this.owneyApiKey = owneyApiKey;
|
|
5133
5411
|
this.routingApiBaseUrl = options.routingApiBaseUrl;
|
|
@@ -5421,6 +5699,7 @@ var YieldseekerAgent = class {
|
|
|
5421
5699
|
this.readCache.clear();
|
|
5422
5700
|
this.pendingReads.clear();
|
|
5423
5701
|
this.portfolioVersions.clear();
|
|
5702
|
+
this.liquidityVersions.clear();
|
|
5424
5703
|
this.snapshotFailures.clear();
|
|
5425
5704
|
this.standardReadFailures.clear();
|
|
5426
5705
|
this.reconcileUntil.clear();
|
|
@@ -5528,7 +5807,7 @@ var YieldseekerAgent = class {
|
|
|
5528
5807
|
notifyFunding(onProgress, evidence);
|
|
5529
5808
|
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
5530
5809
|
}
|
|
5531
|
-
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
5810
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
5532
5811
|
let ownerTransferAttempted = false;
|
|
5533
5812
|
let evidence = {
|
|
5534
5813
|
agentId: this.id,
|
|
@@ -5546,7 +5825,7 @@ var YieldseekerAgent = class {
|
|
|
5546
5825
|
notifyFunding(onProgress, evidence);
|
|
5547
5826
|
}, () => {
|
|
5548
5827
|
ownerTransferAttempted = true;
|
|
5549
|
-
}, gasMode, onBeforeSubmission);
|
|
5828
|
+
}, gasMode, onBeforeSubmission, assertOperation);
|
|
5550
5829
|
} catch (error) {
|
|
5551
5830
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
5552
5831
|
if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.ownerTransferNotExecuted === true || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
|
|
@@ -5559,7 +5838,7 @@ var YieldseekerAgent = class {
|
|
|
5559
5838
|
throw error;
|
|
5560
5839
|
}
|
|
5561
5840
|
}
|
|
5562
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
5841
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
5563
5842
|
this.assertChain(chainId);
|
|
5564
5843
|
this.assertAsset(asset);
|
|
5565
5844
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -5677,7 +5956,7 @@ var YieldseekerAgent = class {
|
|
|
5677
5956
|
const initialFunds = funds;
|
|
5678
5957
|
const idle = initialFunds.idle;
|
|
5679
5958
|
const maxLiquidity = amount === void 0 ? funds : void 0;
|
|
5680
|
-
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.resolvedVaults.includes(position2.
|
|
5959
|
+
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()))];
|
|
5681
5960
|
let positionsStatus = "empty";
|
|
5682
5961
|
const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
|
|
5683
5962
|
validatePositions(positions);
|
|
@@ -5685,18 +5964,18 @@ var YieldseekerAgent = class {
|
|
|
5685
5964
|
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5686
5965
|
const knownPositions = /* @__PURE__ */ new Map();
|
|
5687
5966
|
const rememberPositions = (values) => {
|
|
5688
|
-
for (const position2 of values) knownPositions.set(position2.
|
|
5967
|
+
for (const position2 of values) knownPositions.set(position2.vaultOption.address.toLowerCase(), position2);
|
|
5689
5968
|
};
|
|
5690
5969
|
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5691
5970
|
const verifiedPositions = [];
|
|
5692
5971
|
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5693
|
-
let
|
|
5694
|
-
const checks = this.readVaultShares(values.map((position2) => position2.
|
|
5972
|
+
let active2 = true;
|
|
5973
|
+
const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
|
|
5695
5974
|
const reads = values.map(async (position2, index) => {
|
|
5696
|
-
const address = getAddress5(position2.
|
|
5975
|
+
const address = getAddress5(position2.vaultOption.address);
|
|
5697
5976
|
try {
|
|
5698
5977
|
const group = (await checks).slice(index * 2, index * 2 + 2);
|
|
5699
|
-
if (!
|
|
5978
|
+
if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
|
|
5700
5979
|
this.assertSession(generation, chainId, asset);
|
|
5701
5980
|
const [underlying, shares] = group.map((value) => value.result);
|
|
5702
5981
|
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
|
|
@@ -5712,7 +5991,7 @@ var YieldseekerAgent = class {
|
|
|
5712
5991
|
args: [shares],
|
|
5713
5992
|
blockNumber
|
|
5714
5993
|
});
|
|
5715
|
-
if (!
|
|
5994
|
+
if (!active2 || typeof value !== "bigint" || value < 0n) return null;
|
|
5716
5995
|
this.assertSession(generation, chainId, asset);
|
|
5717
5996
|
if (value > 0n && !positiveValue) {
|
|
5718
5997
|
positiveValue = true;
|
|
@@ -5740,7 +6019,7 @@ var YieldseekerAgent = class {
|
|
|
5740
6019
|
}
|
|
5741
6020
|
});
|
|
5742
6021
|
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5743
|
-
|
|
6022
|
+
active2 = false;
|
|
5744
6023
|
this.assertSession(generation, chainId, asset);
|
|
5745
6024
|
return {
|
|
5746
6025
|
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
@@ -5752,13 +6031,14 @@ var YieldseekerAgent = class {
|
|
|
5752
6031
|
const response = await this.walletRequest(
|
|
5753
6032
|
state,
|
|
5754
6033
|
chainId,
|
|
5755
|
-
this.agentPath(context, "
|
|
6034
|
+
this.agentPath(context, "vault-positions")
|
|
5756
6035
|
);
|
|
5757
6036
|
this.assertSession(generation, chainId, asset);
|
|
5758
|
-
if (!Array.isArray(response?.
|
|
5759
|
-
return validatePositions(response.
|
|
6037
|
+
if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
|
|
6038
|
+
return validatePositions(response.vaultPositions);
|
|
5760
6039
|
};
|
|
5761
6040
|
if (amount === void 0) {
|
|
6041
|
+
rememberPositions(priorPositions);
|
|
5762
6042
|
rememberPositions(funds.indexedPositions);
|
|
5763
6043
|
rememberPositions(positions);
|
|
5764
6044
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
@@ -5785,7 +6065,7 @@ var YieldseekerAgent = class {
|
|
|
5785
6065
|
throw new OwneyError(
|
|
5786
6066
|
"WITHDRAW_FAILED",
|
|
5787
6067
|
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5788
|
-
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.
|
|
6068
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.vaultOption.address) },
|
|
5789
6069
|
this.id
|
|
5790
6070
|
);
|
|
5791
6071
|
}
|
|
@@ -5834,6 +6114,7 @@ var YieldseekerAgent = class {
|
|
|
5834
6114
|
let latestUnwindBlock;
|
|
5835
6115
|
const unwind = async (position2, assetsRaw) => {
|
|
5836
6116
|
this.assertSession(generation, chainId, asset);
|
|
6117
|
+
assertOperation?.();
|
|
5837
6118
|
let unwindTransactionHash;
|
|
5838
6119
|
try {
|
|
5839
6120
|
const response = await this.walletRequest(
|
|
@@ -5842,7 +6123,8 @@ var YieldseekerAgent = class {
|
|
|
5842
6123
|
this.agentPath(context, "withdraw-from-position"),
|
|
5843
6124
|
{
|
|
5844
6125
|
method: "POST",
|
|
5845
|
-
body: { chainId, vaultAddress: position2.
|
|
6126
|
+
body: { chainId, vaultAddress: position2.vaultOption.address, assetsRaw: assetsRaw.toString() },
|
|
6127
|
+
assertCurrent: assertOperation
|
|
5846
6128
|
}
|
|
5847
6129
|
);
|
|
5848
6130
|
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
@@ -5861,7 +6143,7 @@ var YieldseekerAgent = class {
|
|
|
5861
6143
|
...error instanceof OwneyError ? error.details : {},
|
|
5862
6144
|
withdrawalPhase: "unwind",
|
|
5863
6145
|
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5864
|
-
vaultAddress: position2.
|
|
6146
|
+
vaultAddress: position2.vaultOption.address,
|
|
5865
6147
|
unwindTransactionHash,
|
|
5866
6148
|
ownerTransferSubmitted: false
|
|
5867
6149
|
},
|
|
@@ -5874,7 +6156,7 @@ var YieldseekerAgent = class {
|
|
|
5874
6156
|
};
|
|
5875
6157
|
if (amount === void 0) {
|
|
5876
6158
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
5877
|
-
const before = new Map(positions.map((position2) => [position2.
|
|
6159
|
+
const before = new Map(positions.map((position2) => [position2.vaultOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
5878
6160
|
let unwound = false;
|
|
5879
6161
|
for (const position2 of positions) {
|
|
5880
6162
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -5891,16 +6173,16 @@ var YieldseekerAgent = class {
|
|
|
5891
6173
|
if (catalog) rememberPositions(catalog);
|
|
5892
6174
|
this.assertSession(generation, chainId, asset);
|
|
5893
6175
|
if (proof.verified) {
|
|
5894
|
-
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.
|
|
6176
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.vaultOption.address.toLowerCase()))];
|
|
5895
6177
|
} else if (catalog) {
|
|
5896
|
-
const checked = new Set(proof.positions.map((position2) => position2.
|
|
5897
|
-
positions = [...proof.positions, ...catalog.filter((position2) => !checked.has(position2.
|
|
6178
|
+
const checked = new Set(proof.positions.map((position2) => position2.vaultOption.address.toLowerCase()));
|
|
6179
|
+
positions = [...proof.positions, ...catalog.filter((position2) => !checked.has(position2.vaultOption.address.toLowerCase()))];
|
|
5898
6180
|
} else {
|
|
5899
6181
|
positionsStatus = "unavailable";
|
|
5900
6182
|
break;
|
|
5901
6183
|
}
|
|
5902
6184
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5903
|
-
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.
|
|
6185
|
+
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.vaultOption.address.toLowerCase()))) break;
|
|
5904
6186
|
}
|
|
5905
6187
|
} else {
|
|
5906
6188
|
for (const position2 of positions) {
|
|
@@ -5913,7 +6195,7 @@ var YieldseekerAgent = class {
|
|
|
5913
6195
|
}
|
|
5914
6196
|
}
|
|
5915
6197
|
if (amount !== void 0 && remaining > 0n) {
|
|
5916
|
-
throw this.invalidResponse("
|
|
6198
|
+
throw this.invalidResponse("vault positions", {
|
|
5917
6199
|
reason: "Withdrawable positions could not cover the request.",
|
|
5918
6200
|
remaining: remaining.toString()
|
|
5919
6201
|
});
|
|
@@ -5948,6 +6230,13 @@ var YieldseekerAgent = class {
|
|
|
5948
6230
|
const account = getAddress5(state.walletAddress);
|
|
5949
6231
|
let receipt;
|
|
5950
6232
|
const base7 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount ?? "MAX", transfer: "pending", registration: "not-applicable", delivery: "pending" };
|
|
6233
|
+
if (amount === void 0) base7.maxCompletion = {
|
|
6234
|
+
userId: context.user.userId,
|
|
6235
|
+
agentId: context.agent.agentId,
|
|
6236
|
+
vaults: [...knownPositions.keys()],
|
|
6237
|
+
inventoryComplete: funds.complete
|
|
6238
|
+
};
|
|
6239
|
+
if (base7.maxCompletion) notifyFunding(onProgress, base7);
|
|
5951
6240
|
let submissionEvidence = base7;
|
|
5952
6241
|
const data = amount === void 0 ? encodeFunctionData2({
|
|
5953
6242
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
@@ -5965,6 +6254,7 @@ var YieldseekerAgent = class {
|
|
|
5965
6254
|
client: this.getReceiptClient(),
|
|
5966
6255
|
apiKey: this.owneyApiKey,
|
|
5967
6256
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
6257
|
+
assertCurrent: assertOperation,
|
|
5968
6258
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5969
6259
|
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
5970
6260
|
userId: context.user.userId,
|
|
@@ -5977,6 +6267,7 @@ var YieldseekerAgent = class {
|
|
|
5977
6267
|
notifyFunding(onProgress, submissionEvidence);
|
|
5978
6268
|
},
|
|
5979
6269
|
onBeforeSubmission: async (pending) => {
|
|
6270
|
+
assertOperation?.();
|
|
5980
6271
|
await onBeforeSubmission?.({ ...base7, ...pending });
|
|
5981
6272
|
onOwnerTransferAttempted?.();
|
|
5982
6273
|
}
|
|
@@ -5985,15 +6276,26 @@ var YieldseekerAgent = class {
|
|
|
5985
6276
|
receipt = submitted.receipt;
|
|
5986
6277
|
} else {
|
|
5987
6278
|
onOwnerTransferAttempted?.();
|
|
5988
|
-
txHash = await this.submitTransaction(
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
6279
|
+
txHash = await this.submitTransaction(
|
|
6280
|
+
state,
|
|
6281
|
+
chainId,
|
|
6282
|
+
{
|
|
6283
|
+
from: account,
|
|
6284
|
+
to: getAddress5(context.wallet.walletAddress),
|
|
6285
|
+
data,
|
|
6286
|
+
value: "0",
|
|
6287
|
+
chainId
|
|
6288
|
+
},
|
|
6289
|
+
(hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
|
|
6290
|
+
(value) => {
|
|
6291
|
+
receipt = value;
|
|
6292
|
+
},
|
|
6293
|
+
async () => {
|
|
6294
|
+
assertOperation?.();
|
|
6295
|
+
await onBeforeSubmission?.(base7);
|
|
6296
|
+
assertOperation?.();
|
|
6297
|
+
}
|
|
6298
|
+
);
|
|
5997
6299
|
}
|
|
5998
6300
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
|
|
5999
6301
|
notifyFunding(onProgress, evidence);
|
|
@@ -6038,7 +6340,7 @@ var YieldseekerAgent = class {
|
|
|
6038
6340
|
asset,
|
|
6039
6341
|
positionsStatus,
|
|
6040
6342
|
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
6041
|
-
vaultAddress: position2.
|
|
6343
|
+
vaultAddress: position2.vaultOption.address,
|
|
6042
6344
|
sharesRaw: position2.assetsRaw,
|
|
6043
6345
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
6044
6346
|
})) : void 0,
|
|
@@ -6047,6 +6349,7 @@ var YieldseekerAgent = class {
|
|
|
6047
6349
|
this.id
|
|
6048
6350
|
);
|
|
6049
6351
|
}
|
|
6352
|
+
if (evidence.maxCompletion) evidence.maxCompletion.vaults = [...knownPositions.keys()];
|
|
6050
6353
|
if (!proof.verified) completion = "unverified";
|
|
6051
6354
|
}
|
|
6052
6355
|
if (explicitEmptyPayout)
|
|
@@ -6104,6 +6407,65 @@ var YieldseekerAgent = class {
|
|
|
6104
6407
|
);
|
|
6105
6408
|
return proof.available;
|
|
6106
6409
|
}
|
|
6410
|
+
/** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
|
|
6411
|
+
async verifyWithdrawalCompletion(state, evidence) {
|
|
6412
|
+
const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
|
|
6413
|
+
const proof = evidence.maxCompletion;
|
|
6414
|
+
if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !isAddress4(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown("PAYOUT_PROOF_UNAVAILABLE");
|
|
6415
|
+
this.assertAsset(evidence.token);
|
|
6416
|
+
const asset = evidence.token;
|
|
6417
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
6418
|
+
const signature = this.auth.peekToken(state, evidence.chainId);
|
|
6419
|
+
if (!signature) return unknown("AUTHENTICATION_REQUIRED");
|
|
6420
|
+
let reason = "CATALOG_UNAVAILABLE";
|
|
6421
|
+
try {
|
|
6422
|
+
return await bounded(async () => {
|
|
6423
|
+
const response = await this.api.request(
|
|
6424
|
+
`/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
|
|
6425
|
+
{ signature }
|
|
6426
|
+
);
|
|
6427
|
+
if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
|
|
6428
|
+
const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
|
|
6429
|
+
const vaults = new Set([...proof.vaults, ...positions.map((p) => p.vaultOption.address)].map((a) => a.toLowerCase()));
|
|
6430
|
+
if (!proof.inventoryComplete) {
|
|
6431
|
+
const catalog = await this.loadVaultOptions(asset);
|
|
6432
|
+
const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && isAddress4(o.address));
|
|
6433
|
+
if (!eligible.length) return unknown("INVENTORY_INCOMPLETE");
|
|
6434
|
+
eligible.forEach((o) => vaults.add(o.address.toLowerCase()));
|
|
6435
|
+
}
|
|
6436
|
+
if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults].some((a) => !isAddress4(a)))
|
|
6437
|
+
return unknown("INVENTORY_INVALID");
|
|
6438
|
+
reason = "VAULT_PROOF_UNAVAILABLE";
|
|
6439
|
+
const blockNumber = BigInt(evidence.blockNumber);
|
|
6440
|
+
const addresses = [...vaults];
|
|
6441
|
+
const shares = await this.readVaultShares(addresses.map((address) => ({ address })), evidence.agentWallet, blockNumber);
|
|
6442
|
+
const checks = await Promise.all(addresses.map(async (address, i) => {
|
|
6443
|
+
const group = shares.slice(i * 2, i * 2 + 2);
|
|
6444
|
+
if (group.length !== 2 || group.some((r) => r.status !== "success")) return "unavailable";
|
|
6445
|
+
const [underlying, value] = group.map((r) => r.result);
|
|
6446
|
+
if (typeof underlying !== "string" || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof value !== "bigint" || value < 0n) return "unavailable";
|
|
6447
|
+
if (value === 0n) return "complete";
|
|
6448
|
+
const amounts = await Promise.allSettled(["convertToAssets", "previewRedeem"].map((functionName) => this.getReceiptClient().readContract({
|
|
6449
|
+
address: getAddress5(address),
|
|
6450
|
+
abi: erc4626Abi2,
|
|
6451
|
+
functionName,
|
|
6452
|
+
args: [value],
|
|
6453
|
+
blockNumber
|
|
6454
|
+
})));
|
|
6455
|
+
if (amounts.some((r) => r.status === "fulfilled" && typeof r.value === "bigint" && r.value > 0n)) return "remaining";
|
|
6456
|
+
return amounts.every((r) => r.status === "fulfilled" && r.value === 0n) ? "complete" : "unavailable";
|
|
6457
|
+
}));
|
|
6458
|
+
const status = checks.includes("remaining") ? "remaining" : checks.includes("unavailable") ? "unavailable" : "complete";
|
|
6459
|
+
return {
|
|
6460
|
+
status,
|
|
6461
|
+
checkedVaults: addresses.length,
|
|
6462
|
+
...status === "unavailable" ? { reason } : status === "remaining" ? { reason: "VAULT_REMAINDER" } : {}
|
|
6463
|
+
};
|
|
6464
|
+
}, 8e3, () => new Error("MAX proof read timed out"));
|
|
6465
|
+
} catch {
|
|
6466
|
+
return unknown(reason);
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6107
6469
|
async readVaultShares(options, wallet, blockNumber) {
|
|
6108
6470
|
const owner = getAddress5(wallet);
|
|
6109
6471
|
const batches = Array.from({ length: Math.ceil(options.length / YIELDSEEKER_VAULTS_PER_RPC_BATCH) }, (_, index) => options.slice(index * YIELDSEEKER_VAULTS_PER_RPC_BATCH, (index + 1) * YIELDSEEKER_VAULTS_PER_RPC_BATCH));
|
|
@@ -6124,23 +6486,23 @@ var YieldseekerAgent = class {
|
|
|
6124
6486
|
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
6125
6487
|
const seen = /* @__PURE__ */ new Set();
|
|
6126
6488
|
for (const value of values) {
|
|
6127
|
-
const option = value?.
|
|
6489
|
+
const option = value?.vaultOption;
|
|
6128
6490
|
if (!option || typeof option.address !== "string" || !isAddress4(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
|
|
6129
|
-
throw this.invalidResponse("
|
|
6491
|
+
throw this.invalidResponse("vault positions");
|
|
6130
6492
|
seen.add(option.address.toLowerCase());
|
|
6131
6493
|
}
|
|
6132
6494
|
return values;
|
|
6133
6495
|
}
|
|
6134
6496
|
withdrawalVaultHints(state, chainId, context) {
|
|
6135
6497
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6136
|
-
const positions = this.readCache.get(`positions:${key2}`)?.value?.
|
|
6137
|
-
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.
|
|
6498
|
+
const positions = this.readCache.get(`positions:${key2}`)?.value?.vaultPositions ?? [];
|
|
6499
|
+
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
|
|
6138
6500
|
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
6139
6501
|
const receivedAssets = new Set((historic?.movements ?? []).filter((movement) => movement.chainId === chainId && movement.agentWalletId === context.wallet.agentWalletId && movement.toAddress?.toLowerCase() === context.wallet.walletAddress.toLowerCase()).filter((movement) => typeof movement.assetAddress === "string").map((movement) => movement.assetAddress.toLowerCase()));
|
|
6140
|
-
for (const option of this.
|
|
6502
|
+
for (const option of this.vaultOptions.get(context.asset)?.value ?? []) {
|
|
6141
6503
|
if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !isAddress4(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
|
|
6142
6504
|
hints.set(option.address.toLowerCase(), {
|
|
6143
|
-
|
|
6505
|
+
vaultOption: option,
|
|
6144
6506
|
assetsRaw: "0",
|
|
6145
6507
|
assetsBase: "0",
|
|
6146
6508
|
assetsUsd: 0,
|
|
@@ -6174,7 +6536,7 @@ var YieldseekerAgent = class {
|
|
|
6174
6536
|
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
6175
6537
|
const generation = this.readGeneration;
|
|
6176
6538
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
6177
|
-
const version = this.
|
|
6539
|
+
const version = this.liquidityVersions.get(contextKey) ?? 0;
|
|
6178
6540
|
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
6179
6541
|
const unavailable = () => new OwneyError(
|
|
6180
6542
|
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
@@ -6182,12 +6544,12 @@ var YieldseekerAgent = class {
|
|
|
6182
6544
|
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
6183
6545
|
this.id
|
|
6184
6546
|
);
|
|
6185
|
-
let
|
|
6547
|
+
let active2 = true;
|
|
6186
6548
|
let knownProof;
|
|
6187
|
-
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.
|
|
6549
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
6188
6550
|
const assertReadCurrent = () => {
|
|
6189
6551
|
this.assertSession(generation, chainId, context.asset);
|
|
6190
|
-
if (!
|
|
6552
|
+
if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
|
|
6191
6553
|
};
|
|
6192
6554
|
const read2 = async () => {
|
|
6193
6555
|
const client = this.getReceiptClient();
|
|
@@ -6198,8 +6560,8 @@ var YieldseekerAgent = class {
|
|
|
6198
6560
|
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
6199
6561
|
const candidates = /* @__PURE__ */ new Map();
|
|
6200
6562
|
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
6201
|
-
candidates.set(position2.
|
|
6202
|
-
for (const position2 of indexedPositions) candidates.set(position2.
|
|
6563
|
+
candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
|
|
6564
|
+
for (const position2 of indexedPositions) candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
|
|
6203
6565
|
const options = [...candidates.values()];
|
|
6204
6566
|
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
6205
6567
|
const [idle, feeTracker] = await Promise.all([
|
|
@@ -6243,15 +6605,15 @@ var YieldseekerAgent = class {
|
|
|
6243
6605
|
assertReadCurrent();
|
|
6244
6606
|
knownProof = proof(false);
|
|
6245
6607
|
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6246
|
-
const indexed = new Map(indexedPositions.map((position2) => [position2.
|
|
6608
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
|
|
6247
6609
|
const indexedValue = indexedPositions.reduce((sum, position2) => sum + BigInt(position2.withdrawableAssetsRaw), 0n);
|
|
6248
|
-
const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.
|
|
6610
|
+
const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.vaultOption.address.toLowerCase()));
|
|
6249
6611
|
const unvaluedPosition = indexedPositions.some((position2) => BigInt(position2.assetsRaw) > 0n && BigInt(position2.withdrawableAssetsRaw) === 0n);
|
|
6250
6612
|
const indexedAvailable = idle + indexedValue > fees ? idle + indexedValue - fees : 0n;
|
|
6251
6613
|
if (indexedValue > 0n && !missingKnownPosition && !unvaluedPosition && (requested === void 0 || indexedAvailable >= requested)) {
|
|
6252
6614
|
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
6253
6615
|
for (const position2 of indexedPositions) if (BigInt(position2.withdrawableAssetsRaw) > 0n)
|
|
6254
|
-
known.add(position2.
|
|
6616
|
+
known.add(position2.vaultOption.address.toLowerCase());
|
|
6255
6617
|
this.balanceVaults.set(contextKey, known);
|
|
6256
6618
|
return {
|
|
6257
6619
|
blockNumber,
|
|
@@ -6275,7 +6637,7 @@ var YieldseekerAgent = class {
|
|
|
6275
6637
|
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
|
|
6276
6638
|
if (shares > 0n) known.add(option.address.toLowerCase());
|
|
6277
6639
|
const position2 = {
|
|
6278
|
-
|
|
6640
|
+
vaultOption: option,
|
|
6279
6641
|
assetsRaw: shares.toString(),
|
|
6280
6642
|
assetsBase: "0",
|
|
6281
6643
|
assetsUsd: 0,
|
|
@@ -6331,7 +6693,7 @@ var YieldseekerAgent = class {
|
|
|
6331
6693
|
knownProof = proof(false);
|
|
6332
6694
|
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6333
6695
|
const discrepancy = verified.some((position2) => {
|
|
6334
|
-
const row = indexed.get(position2.
|
|
6696
|
+
const row = indexed.get(position2.vaultOption.address.toLowerCase());
|
|
6335
6697
|
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
6336
6698
|
});
|
|
6337
6699
|
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
@@ -6340,7 +6702,7 @@ var YieldseekerAgent = class {
|
|
|
6340
6702
|
assertReadCurrent();
|
|
6341
6703
|
return proof(true);
|
|
6342
6704
|
}
|
|
6343
|
-
const catalog = await this.
|
|
6705
|
+
const catalog = await this.loadVaultOptions(context.asset).catch(() => void 0);
|
|
6344
6706
|
assertReadCurrent();
|
|
6345
6707
|
const eligible = /* @__PURE__ */ new Map();
|
|
6346
6708
|
for (const option of catalog ?? []) {
|
|
@@ -6366,7 +6728,7 @@ var YieldseekerAgent = class {
|
|
|
6366
6728
|
if (error instanceof OwneyError) throw error;
|
|
6367
6729
|
throw unavailable();
|
|
6368
6730
|
} finally {
|
|
6369
|
-
|
|
6731
|
+
active2 = false;
|
|
6370
6732
|
if (timer) clearTimeout(timer);
|
|
6371
6733
|
}
|
|
6372
6734
|
}
|
|
@@ -6380,7 +6742,7 @@ var YieldseekerAgent = class {
|
|
|
6380
6742
|
this.balanceContexts.set(read2, { context, generation: this.readGeneration });
|
|
6381
6743
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6382
6744
|
const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
|
|
6383
|
-
for (const position2 of context.positions) known.add(position2.
|
|
6745
|
+
for (const position2 of context.positions) known.add(position2.vaultOption.address.toLowerCase());
|
|
6384
6746
|
this.balanceVaults.set(key2, known);
|
|
6385
6747
|
}
|
|
6386
6748
|
return balance;
|
|
@@ -6419,7 +6781,7 @@ var YieldseekerAgent = class {
|
|
|
6419
6781
|
const total = idle + invested > fees ? idle + invested - fees : 0n;
|
|
6420
6782
|
const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
6421
6783
|
if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
|
|
6422
|
-
const amount =
|
|
6784
|
+
const amount = parseUnits2(displayed.amount, asset.decimals);
|
|
6423
6785
|
if (amount < 0n) return void 0;
|
|
6424
6786
|
const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
|
|
6425
6787
|
const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
|
|
@@ -6518,13 +6880,13 @@ var YieldseekerAgent = class {
|
|
|
6518
6880
|
});
|
|
6519
6881
|
const catalog = await Promise.all(
|
|
6520
6882
|
[...new Set(contexts.map((context) => context.asset))].map(
|
|
6521
|
-
(contextAsset) => this.
|
|
6883
|
+
(contextAsset) => this.loadVaultOptions(contextAsset)
|
|
6522
6884
|
)
|
|
6523
6885
|
);
|
|
6524
6886
|
const vaultAddresses = new Set(
|
|
6525
6887
|
catalog.flat().filter(
|
|
6526
|
-
(
|
|
6527
|
-
).map((
|
|
6888
|
+
(vaultOption) => vaultOption.chainId === chainId && isAddress4(vaultOption.address)
|
|
6889
|
+
).map((vaultOption) => vaultOption.address.toLowerCase())
|
|
6528
6890
|
);
|
|
6529
6891
|
return mapYieldseekerHistory(contexts, {
|
|
6530
6892
|
limit: options?.limit ?? 10,
|
|
@@ -6549,30 +6911,30 @@ var YieldseekerAgent = class {
|
|
|
6549
6911
|
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
6550
6912
|
const values = await Promise.all(
|
|
6551
6913
|
assets.map(async (asset) => {
|
|
6552
|
-
return { asset,
|
|
6914
|
+
return { asset, vaultOptions: await this.loadVaultOptions(asset) };
|
|
6553
6915
|
})
|
|
6554
6916
|
);
|
|
6555
6917
|
return mapYieldseekerAgentApy(values, days);
|
|
6556
6918
|
}
|
|
6557
|
-
async
|
|
6558
|
-
const cached = this.
|
|
6919
|
+
async loadVaultOptions(asset) {
|
|
6920
|
+
const cached = this.vaultOptions.get(asset);
|
|
6559
6921
|
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
6560
|
-
const pending = this.
|
|
6922
|
+
const pending = this.pendingVaultOptions.get(asset);
|
|
6561
6923
|
if (pending) return pending;
|
|
6562
6924
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
6563
6925
|
const request2 = this.api.request(
|
|
6564
|
-
`/chains/8453/assets/${metadata.address}/
|
|
6926
|
+
`/chains/8453/assets/${metadata.address}/vault-options`
|
|
6565
6927
|
).then((response) => {
|
|
6566
|
-
if (!Array.isArray(response?.
|
|
6567
|
-
throw this.invalidResponse("
|
|
6928
|
+
if (!Array.isArray(response?.vaultOptions)) {
|
|
6929
|
+
throw this.invalidResponse("vault options");
|
|
6568
6930
|
}
|
|
6569
|
-
this.
|
|
6570
|
-
expiresAt: Date.now() +
|
|
6571
|
-
value: response.
|
|
6931
|
+
this.vaultOptions.set(asset, {
|
|
6932
|
+
expiresAt: Date.now() + YIELDSEEKER_VAULT_OPTIONS_CACHE_MS,
|
|
6933
|
+
value: response.vaultOptions
|
|
6572
6934
|
});
|
|
6573
|
-
return response.
|
|
6574
|
-
}).finally(() => this.
|
|
6575
|
-
this.
|
|
6935
|
+
return response.vaultOptions;
|
|
6936
|
+
}).finally(() => this.pendingVaultOptions.delete(asset));
|
|
6937
|
+
this.pendingVaultOptions.set(asset, request2);
|
|
6576
6938
|
return request2;
|
|
6577
6939
|
}
|
|
6578
6940
|
userKey(state, chainId) {
|
|
@@ -6882,9 +7244,10 @@ var YieldseekerAgent = class {
|
|
|
6882
7244
|
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
6883
7245
|
};
|
|
6884
7246
|
}
|
|
6885
|
-
advancePortfolioVersion(contextKey) {
|
|
7247
|
+
advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
|
|
6886
7248
|
const version = this.portfolioVersion(contextKey) + 1;
|
|
6887
7249
|
this.portfolioVersions.set(contextKey, version);
|
|
7250
|
+
if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
|
|
6888
7251
|
this.snapshotFailures.delete(contextKey);
|
|
6889
7252
|
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
6890
7253
|
const key2 = `${kind}:${contextKey}`;
|
|
@@ -6932,7 +7295,7 @@ var YieldseekerAgent = class {
|
|
|
6932
7295
|
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
6933
7296
|
const failure = this.snapshotFailures.get(contextKey);
|
|
6934
7297
|
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
6935
|
-
this.advancePortfolioVersion(contextKey);
|
|
7298
|
+
this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
|
|
6936
7299
|
}
|
|
6937
7300
|
const version = this.portfolioVersion(contextKey);
|
|
6938
7301
|
const generation = this.readGeneration;
|
|
@@ -7015,17 +7378,17 @@ var YieldseekerAgent = class {
|
|
|
7015
7378
|
}
|
|
7016
7379
|
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
7017
7380
|
const [positions, historic, actions] = await Promise.all([
|
|
7018
|
-
options.includePositions === false ? Promise.resolve({
|
|
7381
|
+
options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
|
|
7019
7382
|
`positions:${contextKey}`,
|
|
7020
7383
|
this.portfolioCacheMs(contextKey),
|
|
7021
7384
|
async () => {
|
|
7022
7385
|
const response = await this.walletRequest(
|
|
7023
7386
|
state,
|
|
7024
7387
|
chainId,
|
|
7025
|
-
this.agentPath(context, "
|
|
7388
|
+
this.agentPath(context, "vault-positions")
|
|
7026
7389
|
);
|
|
7027
|
-
if (!Array.isArray(response?.
|
|
7028
|
-
throw this.invalidResponse("
|
|
7390
|
+
if (!Array.isArray(response?.vaultPositions)) {
|
|
7391
|
+
throw this.invalidResponse("vault positions");
|
|
7029
7392
|
}
|
|
7030
7393
|
return response;
|
|
7031
7394
|
}
|
|
@@ -7050,7 +7413,7 @@ var YieldseekerAgent = class {
|
|
|
7050
7413
|
) : Promise.resolve(void 0)
|
|
7051
7414
|
]);
|
|
7052
7415
|
return {
|
|
7053
|
-
positions: positions.
|
|
7416
|
+
positions: positions.vaultPositions,
|
|
7054
7417
|
...historic?.position ? { historic: historic.position } : {},
|
|
7055
7418
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
7056
7419
|
};
|
|
@@ -7121,10 +7484,10 @@ var YieldseekerAgent = class {
|
|
|
7121
7484
|
}
|
|
7122
7485
|
async providerRequest(state, chainId, path, options = {}) {
|
|
7123
7486
|
this.assertChain(chainId);
|
|
7124
|
-
const request2 = (signature2) =>
|
|
7125
|
-
|
|
7126
|
-
signature: signature2
|
|
7127
|
-
}
|
|
7487
|
+
const request2 = (signature2) => {
|
|
7488
|
+
options.assertCurrent?.();
|
|
7489
|
+
return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
|
|
7490
|
+
};
|
|
7128
7491
|
let signature = await this.auth.getToken(state, chainId);
|
|
7129
7492
|
try {
|
|
7130
7493
|
return await request2(signature);
|
|
@@ -7426,45 +7789,57 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
|
7426
7789
|
// src/lib/swap/swap-api.ts
|
|
7427
7790
|
async function request(baseUrl, apiKey, path, init) {
|
|
7428
7791
|
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
7429
|
-
const
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7792
|
+
const controller = new AbortController();
|
|
7793
|
+
try {
|
|
7794
|
+
return await bounded(async () => {
|
|
7795
|
+
const res = await fetch(url, {
|
|
7796
|
+
signal: controller.signal,
|
|
7797
|
+
method: init?.method ?? "GET",
|
|
7798
|
+
headers: {
|
|
7799
|
+
"Content-Type": "application/json",
|
|
7800
|
+
"x-owney-api-key": apiKey
|
|
7801
|
+
},
|
|
7802
|
+
...init ? { body: JSON.stringify(init.body) } : {}
|
|
7803
|
+
});
|
|
7804
|
+
if (!res.ok) {
|
|
7805
|
+
const text = await res.text().catch(() => "");
|
|
7806
|
+
if (res.status === 429) {
|
|
7807
|
+
throw new OwneyError(
|
|
7808
|
+
"SWAP_RATE_LIMITED",
|
|
7809
|
+
"Swap provider is rate limiting, retry shortly",
|
|
7810
|
+
{ statusCode: res.status }
|
|
7811
|
+
);
|
|
7812
|
+
}
|
|
7813
|
+
if (res.status === 403) {
|
|
7814
|
+
throw new OwneyError(
|
|
7815
|
+
"SWAP_DISABLED",
|
|
7816
|
+
"Swap is not enabled for this organization",
|
|
7817
|
+
{ statusCode: res.status }
|
|
7818
|
+
);
|
|
7819
|
+
}
|
|
7820
|
+
throw new OwneyError(
|
|
7821
|
+
"SWAP_REQUEST_FAILED",
|
|
7822
|
+
`Swap API error ${res.status}: ${text}`,
|
|
7823
|
+
{ statusCode: res.status, responseBody: text }
|
|
7824
|
+
);
|
|
7825
|
+
}
|
|
7826
|
+
const json = await res.json();
|
|
7827
|
+
if (!json.success) {
|
|
7828
|
+
throw new OwneyError(
|
|
7829
|
+
"SWAP_REQUEST_FAILED",
|
|
7830
|
+
`Swap API request failed: ${json.message ?? "unknown error"}`,
|
|
7831
|
+
{ message: json.message }
|
|
7832
|
+
);
|
|
7833
|
+
}
|
|
7834
|
+
return json.data;
|
|
7835
|
+
}, 2e4, () => new OwneyError(
|
|
7462
7836
|
"SWAP_REQUEST_FAILED",
|
|
7463
|
-
|
|
7464
|
-
{
|
|
7465
|
-
);
|
|
7837
|
+
"Swap request timed out. Its outcome may still need confirmation.",
|
|
7838
|
+
{ requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
|
|
7839
|
+
));
|
|
7840
|
+
} finally {
|
|
7841
|
+
controller.abort();
|
|
7466
7842
|
}
|
|
7467
|
-
return json.data;
|
|
7468
7843
|
}
|
|
7469
7844
|
function createSwapApi(baseUrl, apiKey) {
|
|
7470
7845
|
return {
|
|
@@ -7770,8 +8145,12 @@ async function awaitBalanceIncrease(options) {
|
|
|
7770
8145
|
let lastError;
|
|
7771
8146
|
for (; ; ) {
|
|
7772
8147
|
try {
|
|
7773
|
-
const balance = await
|
|
7774
|
-
|
|
8148
|
+
const balance = await bounded(
|
|
8149
|
+
readBalance,
|
|
8150
|
+
Math.max(1, Math.min(1e4, deadline - Date.now())),
|
|
8151
|
+
() => new Error("Balance observation timed out")
|
|
8152
|
+
);
|
|
8153
|
+
if (Date.now() <= deadline && balance > baseline) {
|
|
7775
8154
|
const arrived = balance - baseline;
|
|
7776
8155
|
debugLog("owney-sdk", "arrival: funds landed", {
|
|
7777
8156
|
arrived: arrived.toString()
|
|
@@ -7797,7 +8176,7 @@ async function awaitBalanceIncrease(options) {
|
|
|
7797
8176
|
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
7798
8177
|
} : { arrived: null };
|
|
7799
8178
|
}
|
|
7800
|
-
await sleep(pollMs);
|
|
8179
|
+
await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
7801
8180
|
}
|
|
7802
8181
|
}
|
|
7803
8182
|
|
|
@@ -7850,7 +8229,7 @@ var DEFAULT_POLL_MS2 = 5e3;
|
|
|
7850
8229
|
var MAX_BACKOFF_MS = 3e4;
|
|
7851
8230
|
var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
7852
8231
|
var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
|
|
7853
|
-
async function
|
|
8232
|
+
async function runFusionOrderInternal(deps, options) {
|
|
7854
8233
|
const {
|
|
7855
8234
|
orderHash,
|
|
7856
8235
|
secrets,
|
|
@@ -7859,39 +8238,51 @@ async function runFusionOrder(deps, options) {
|
|
|
7859
8238
|
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
7860
8239
|
} = options;
|
|
7861
8240
|
const deadline = deps.now() + timeoutMs;
|
|
8241
|
+
let partial = options.hasPartialFill === true;
|
|
8242
|
+
const timeout = () => new OwneyError(
|
|
8243
|
+
"SWAP_REQUEST_FAILED",
|
|
8244
|
+
"Swap settlement is not confirmed yet. Check its status before starting another swap.",
|
|
8245
|
+
{ orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
|
|
8246
|
+
);
|
|
8247
|
+
const assertTime = () => {
|
|
8248
|
+
if (deps.now() >= deadline) throw timeout();
|
|
8249
|
+
};
|
|
8250
|
+
const read2 = async (work) => {
|
|
8251
|
+
assertTime();
|
|
8252
|
+
const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
|
|
8253
|
+
assertTime();
|
|
8254
|
+
return result;
|
|
8255
|
+
};
|
|
8256
|
+
const pause = async (ms) => {
|
|
8257
|
+
assertTime();
|
|
8258
|
+
await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
|
|
8259
|
+
assertTime();
|
|
8260
|
+
};
|
|
7862
8261
|
let failures = 0;
|
|
7863
8262
|
const published = /* @__PURE__ */ new Set();
|
|
7864
8263
|
onStage?.("swapping");
|
|
7865
8264
|
for (; ; ) {
|
|
7866
|
-
|
|
7867
|
-
throw new OwneyError(
|
|
7868
|
-
"SWAP_REQUEST_FAILED",
|
|
7869
|
-
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
7870
|
-
// Marks the one failure that means "we stopped watching a live order",
|
|
7871
|
-
// as opposed to a verdict or a prompt nobody answered. The executor
|
|
7872
|
-
// checks the wallet before believing it: resolvers do not stop filling
|
|
7873
|
-
// because we stopped looking (ROUT-242, comment 15980).
|
|
7874
|
-
{ orderHash, settlementUnconfirmed: true }
|
|
7875
|
-
);
|
|
7876
|
-
}
|
|
8265
|
+
assertTime();
|
|
7877
8266
|
let ready;
|
|
7878
8267
|
try {
|
|
7879
|
-
ready = await deps.readyForSecrets(orderHash);
|
|
8268
|
+
ready = await read2(() => deps.readyForSecrets(orderHash));
|
|
7880
8269
|
} catch {
|
|
7881
8270
|
ready = {};
|
|
7882
8271
|
}
|
|
8272
|
+
assertTime();
|
|
7883
8273
|
for (const fill of ready.fills ?? []) {
|
|
8274
|
+
assertTime();
|
|
7884
8275
|
if (published.has(fill.idx)) continue;
|
|
7885
8276
|
const secret = secrets[fill.idx];
|
|
7886
8277
|
if (secret === void 0) {
|
|
7887
8278
|
throw new OwneyError(
|
|
7888
8279
|
"SWAP_REQUEST_FAILED",
|
|
7889
8280
|
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
7890
|
-
{ orderHash, fillIndex: fill.idx }
|
|
8281
|
+
{ orderHash, fillIndex: fill.idx, settlementUnconfirmed: true, outcome: "submitted-unknown" }
|
|
7891
8282
|
);
|
|
7892
8283
|
}
|
|
7893
8284
|
try {
|
|
7894
|
-
await deps.submitSecret(orderHash, secret);
|
|
8285
|
+
await read2(() => deps.submitSecret(orderHash, secret));
|
|
7895
8286
|
published.add(fill.idx);
|
|
7896
8287
|
} catch {
|
|
7897
8288
|
failures += 1;
|
|
@@ -7899,29 +8290,34 @@ async function runFusionOrder(deps, options) {
|
|
|
7899
8290
|
}
|
|
7900
8291
|
let status;
|
|
7901
8292
|
try {
|
|
7902
|
-
|
|
8293
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8294
|
+
status = state.status;
|
|
8295
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7903
8296
|
failures = 0;
|
|
7904
8297
|
} catch {
|
|
7905
8298
|
failures += 1;
|
|
7906
|
-
await
|
|
8299
|
+
await pause(backoffFor(failures, pollIntervalMs));
|
|
7907
8300
|
continue;
|
|
7908
8301
|
}
|
|
7909
8302
|
if (status === "refunding") onStage?.("refunding");
|
|
7910
8303
|
if (isSwapTerminal(status)) {
|
|
7911
|
-
if (status === "executed") {
|
|
8304
|
+
if (status === "executed" || status === "filled") {
|
|
7912
8305
|
onStage?.("swapped");
|
|
7913
8306
|
return { status, filled: true };
|
|
7914
8307
|
}
|
|
7915
|
-
if (status === "refunded") onStage?.("refunded");
|
|
8308
|
+
if (status === "refunded") onStage?.(partial ? "swap-partial" : "refunded");
|
|
7916
8309
|
throw new OwneyError(
|
|
7917
8310
|
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7918
|
-
status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
|
|
7919
|
-
{ orderHash, status }
|
|
8311
|
+
partial ? "The swap filled partly. Check the order and both balances before retrying." : status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
|
|
8312
|
+
{ orderHash, status, outcome: status === "refunded" && !partial ? "refunded" : partial ? "partially-filled" : "refund-pending" }
|
|
7920
8313
|
);
|
|
7921
8314
|
}
|
|
7922
|
-
await
|
|
8315
|
+
await pause(pollIntervalMs);
|
|
7923
8316
|
}
|
|
7924
8317
|
}
|
|
8318
|
+
function runFusionOrder(deps, options) {
|
|
8319
|
+
return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
|
|
8320
|
+
}
|
|
7925
8321
|
|
|
7926
8322
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
7927
8323
|
var DEFAULT_POLL_MS3 = 5e3;
|
|
@@ -7946,7 +8342,7 @@ function explain(status) {
|
|
|
7946
8342
|
return "The swap did not complete. Nothing was exchanged.";
|
|
7947
8343
|
}
|
|
7948
8344
|
}
|
|
7949
|
-
async function
|
|
8345
|
+
async function runSameChainOrderInternal(deps, options) {
|
|
7950
8346
|
const {
|
|
7951
8347
|
orderHash,
|
|
7952
8348
|
onStage,
|
|
@@ -7954,23 +8350,39 @@ async function runSameChainOrder(deps, options) {
|
|
|
7954
8350
|
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
7955
8351
|
} = options;
|
|
7956
8352
|
const deadline = deps.now() + timeoutMs;
|
|
8353
|
+
let partial = options.hasPartialFill === true;
|
|
8354
|
+
const timeout = () => new OwneyError(
|
|
8355
|
+
"SWAP_REQUEST_FAILED",
|
|
8356
|
+
"Swap settlement is not confirmed yet. Check its status before starting another swap.",
|
|
8357
|
+
{ orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
|
|
8358
|
+
);
|
|
8359
|
+
const assertTime = () => {
|
|
8360
|
+
if (deps.now() >= deadline) throw timeout();
|
|
8361
|
+
};
|
|
8362
|
+
const read2 = async (work) => {
|
|
8363
|
+
assertTime();
|
|
8364
|
+
const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
|
|
8365
|
+
assertTime();
|
|
8366
|
+
return result;
|
|
8367
|
+
};
|
|
8368
|
+
const pause = async (ms) => {
|
|
8369
|
+
assertTime();
|
|
8370
|
+
await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
|
|
8371
|
+
assertTime();
|
|
8372
|
+
};
|
|
7957
8373
|
let failures = 0;
|
|
7958
8374
|
onStage?.("swapping");
|
|
7959
8375
|
for (; ; ) {
|
|
7960
|
-
|
|
7961
|
-
throw new OwneyError(
|
|
7962
|
-
"SWAP_ORDER_EXPIRED",
|
|
7963
|
-
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
7964
|
-
{ orderHash }
|
|
7965
|
-
);
|
|
7966
|
-
}
|
|
8376
|
+
assertTime();
|
|
7967
8377
|
let status;
|
|
7968
8378
|
try {
|
|
7969
|
-
|
|
8379
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8380
|
+
status = state.status;
|
|
8381
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7970
8382
|
failures = 0;
|
|
7971
8383
|
} catch {
|
|
7972
8384
|
failures += 1;
|
|
7973
|
-
await
|
|
8385
|
+
await pause(backoffFor2(failures, pollIntervalMs));
|
|
7974
8386
|
continue;
|
|
7975
8387
|
}
|
|
7976
8388
|
if (isSwapTerminal(status)) {
|
|
@@ -7980,17 +8392,19 @@ async function runSameChainOrder(deps, options) {
|
|
|
7980
8392
|
}
|
|
7981
8393
|
throw new OwneyError(
|
|
7982
8394
|
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7983
|
-
explain(status),
|
|
7984
|
-
{ orderHash, status }
|
|
8395
|
+
partial ? "The swap filled partly. Check the order and both balances before retrying." : explain(status),
|
|
8396
|
+
{ orderHash, status, outcome: partial ? "partially-filled" : "not-submitted" }
|
|
7985
8397
|
);
|
|
7986
8398
|
}
|
|
7987
|
-
await
|
|
8399
|
+
await pause(pollIntervalMs);
|
|
7988
8400
|
}
|
|
7989
8401
|
}
|
|
8402
|
+
function runSameChainOrder(deps, options) {
|
|
8403
|
+
return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
|
|
8404
|
+
}
|
|
7990
8405
|
|
|
7991
8406
|
// src/lib/swap/swap.secret-store.ts
|
|
7992
8407
|
var KEY_PREFIX4 = "owney.swap.order";
|
|
7993
|
-
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7994
8408
|
var storage4 = () => {
|
|
7995
8409
|
if (typeof window === "undefined") return null;
|
|
7996
8410
|
try {
|
|
@@ -8002,10 +8416,13 @@ var storage4 = () => {
|
|
|
8002
8416
|
var keyFor2 = (orderHash) => `${KEY_PREFIX4}.${orderHash}`;
|
|
8003
8417
|
function saveOrder(order) {
|
|
8004
8418
|
const store = storage4();
|
|
8005
|
-
if (!store) return;
|
|
8419
|
+
if (!store) return false;
|
|
8006
8420
|
try {
|
|
8007
|
-
|
|
8421
|
+
const value = JSON.stringify(order);
|
|
8422
|
+
store.setItem(keyFor2(order.orderHash), value);
|
|
8423
|
+
return store.getItem(keyFor2(order.orderHash)) === value;
|
|
8008
8424
|
} catch {
|
|
8425
|
+
return false;
|
|
8009
8426
|
}
|
|
8010
8427
|
}
|
|
8011
8428
|
function clearOrder(orderHash) {
|
|
@@ -8016,7 +8433,7 @@ function clearOrder(orderHash) {
|
|
|
8016
8433
|
} catch {
|
|
8017
8434
|
}
|
|
8018
8435
|
}
|
|
8019
|
-
function listOrders(
|
|
8436
|
+
function listOrders(_now = Date.now()) {
|
|
8020
8437
|
const store = storage4();
|
|
8021
8438
|
if (!store) return [];
|
|
8022
8439
|
const out = [];
|
|
@@ -8031,10 +8448,6 @@ function listOrders(now = Date.now()) {
|
|
|
8031
8448
|
if (!raw2) continue;
|
|
8032
8449
|
try {
|
|
8033
8450
|
const parsed = JSON.parse(raw2);
|
|
8034
|
-
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
8035
|
-
store.removeItem(key2);
|
|
8036
|
-
continue;
|
|
8037
|
-
}
|
|
8038
8451
|
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
8039
8452
|
out.push(parsed);
|
|
8040
8453
|
}
|
|
@@ -8075,12 +8488,11 @@ async function awaitWalletPrompt(prompt, what) {
|
|
|
8075
8488
|
if (timer) clearTimeout(timer);
|
|
8076
8489
|
}
|
|
8077
8490
|
}
|
|
8078
|
-
function stoppedWatching(error) {
|
|
8079
|
-
return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
|
|
8080
|
-
}
|
|
8081
8491
|
var DEFAULT_SLIPPAGE = 1;
|
|
8082
|
-
async function affordableAmount(deps, quoted) {
|
|
8083
|
-
const
|
|
8492
|
+
async function affordableAmount(deps, quoted, reserve = 0n) {
|
|
8493
|
+
const raw2 = await deps.readSourceBalance();
|
|
8494
|
+
const balance = raw2 > reserve ? raw2 - reserve : 0n;
|
|
8495
|
+
if (balance <= 0n) throw new OwneyError("SWAP_REQUEST_FAILED", "No spendable source funds remain for this swap.", { outcome: "not-submitted" });
|
|
8084
8496
|
if (balance >= quoted) return quoted;
|
|
8085
8497
|
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
8086
8498
|
quoted: quoted.toString(),
|
|
@@ -8091,48 +8503,72 @@ async function affordableAmount(deps, quoted) {
|
|
|
8091
8503
|
}
|
|
8092
8504
|
async function executeSwap(deps, options) {
|
|
8093
8505
|
const { quote, walletAddress, onStage } = options;
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8506
|
+
let active2 = true;
|
|
8507
|
+
const original = deps;
|
|
8508
|
+
const assertActive = () => {
|
|
8509
|
+
if (!active2) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
|
|
8510
|
+
};
|
|
8511
|
+
const call = async (work, write = false) => {
|
|
8512
|
+
assertActive();
|
|
8513
|
+
if (write) options.assertCurrent?.();
|
|
8514
|
+
const value = await bounded(
|
|
8515
|
+
work,
|
|
8516
|
+
write ? 3e5 : 2e4,
|
|
8517
|
+
() => new OwneyError(
|
|
8518
|
+
"SWAP_REQUEST_FAILED",
|
|
8519
|
+
"The swap step could not be confirmed. Check its status before retrying.",
|
|
8520
|
+
{ settlementUnconfirmed: write, outcome: write ? "submitted-unknown" : "not-submitted" }
|
|
8521
|
+
)
|
|
8522
|
+
);
|
|
8523
|
+
assertActive();
|
|
8524
|
+
if (write) options.assertCurrent?.();
|
|
8525
|
+
return value;
|
|
8526
|
+
};
|
|
8527
|
+
deps = {
|
|
8528
|
+
...original,
|
|
8529
|
+
api: {
|
|
8530
|
+
...original.api,
|
|
8531
|
+
buildOrder: (args) => call(() => original.api.buildOrder(args)),
|
|
8532
|
+
swapTx: (args) => call(() => original.api.swapTx(args)),
|
|
8533
|
+
submitOrder: (args) => call(() => original.api.submitOrder(args), true)
|
|
8534
|
+
},
|
|
8535
|
+
readTargetBalance: () => call(original.readTargetBalance),
|
|
8536
|
+
readSourceBalance: () => call(original.readSourceBalance),
|
|
8537
|
+
readAllowance: (spender) => call(() => original.readAllowance(spender)),
|
|
8538
|
+
ensureChain: (chain2) => call(() => original.ensureChain(chain2), true),
|
|
8539
|
+
approve: (spender, amount) => call(() => original.approve(spender, amount), true),
|
|
8540
|
+
sendTransaction: (tx) => call(() => original.sendTransaction(tx), true),
|
|
8541
|
+
signTypedData: (data) => call(() => original.signTypedData(data), true),
|
|
8542
|
+
...original.buildPermit ? { buildPermit: (spender, amount) => call(() => original.buildPermit(spender, amount), true) } : {}
|
|
8543
|
+
};
|
|
8105
8544
|
try {
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8545
|
+
debugLog("owney-sdk", "swap: start", {
|
|
8546
|
+
rail: quote.rail,
|
|
8547
|
+
from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
|
|
8548
|
+
to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
|
|
8549
|
+
expected: quote.dst.amount,
|
|
8550
|
+
floor: quote.dstAmountMin
|
|
8112
8551
|
});
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
if (railError) throw railError;
|
|
8122
|
-
throw new OwneyError(
|
|
8123
|
-
"SWAP_REQUEST_FAILED",
|
|
8124
|
-
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
8125
|
-
{ rail: quote.rail, ...result ?? {} }
|
|
8126
|
-
);
|
|
8127
|
-
}
|
|
8128
|
-
if (railError) {
|
|
8129
|
-
debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
|
|
8130
|
-
received: arrived.toString()
|
|
8552
|
+
const before = await deps.readTargetBalance();
|
|
8553
|
+
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
8554
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
8555
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
8556
|
+
readBalance: () => deps.readTargetBalance(),
|
|
8557
|
+
baseline: before,
|
|
8558
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
|
|
8559
|
+
...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
|
|
8131
8560
|
});
|
|
8132
|
-
|
|
8133
|
-
|
|
8561
|
+
if (arrived === null) {
|
|
8562
|
+
throw new OwneyError(
|
|
8563
|
+
"SWAP_REQUEST_FAILED",
|
|
8564
|
+
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
8565
|
+
{ rail: quote.rail, ...result, outcome: "filled", settlementUnconfirmed: true }
|
|
8566
|
+
);
|
|
8567
|
+
}
|
|
8568
|
+
return { received: arrived.toString(), ...result };
|
|
8569
|
+
} finally {
|
|
8570
|
+
active2 = false;
|
|
8134
8571
|
}
|
|
8135
|
-
return { received: arrived.toString(), ...result ?? {} };
|
|
8136
8572
|
}
|
|
8137
8573
|
async function runClassic(deps, options) {
|
|
8138
8574
|
const {
|
|
@@ -8143,7 +8579,7 @@ async function runClassic(deps, options) {
|
|
|
8143
8579
|
onStage
|
|
8144
8580
|
} = options;
|
|
8145
8581
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8146
|
-
|
|
8582
|
+
let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8147
8583
|
const swapTxRequest = {
|
|
8148
8584
|
from: {
|
|
8149
8585
|
chainId: quote.src.chainId,
|
|
@@ -8175,25 +8611,36 @@ async function runClassic(deps, options) {
|
|
|
8175
8611
|
"owney-sdk",
|
|
8176
8612
|
"swap: re-fetching classic calldata after approval"
|
|
8177
8613
|
);
|
|
8614
|
+
amount = await affordableAmount(deps, amount, options.sourceReserve);
|
|
8615
|
+
swapTxRequest.from.amount = amount.toString();
|
|
8178
8616
|
({ tx } = await deps.api.swapTx(swapTxRequest));
|
|
8617
|
+
if (await deps.readAllowance(tx.to) < amount)
|
|
8618
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed for the current spender.", { outcome: "not-submitted" });
|
|
8179
8619
|
}
|
|
8180
8620
|
}
|
|
8181
8621
|
onStage?.("signing");
|
|
8182
8622
|
await deps.ensureChain(quote.src.chainId);
|
|
8183
8623
|
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
8624
|
+
options.assertCurrent?.();
|
|
8625
|
+
options.onTransactionRequested?.();
|
|
8184
8626
|
const txHash = await deps.sendTransaction({
|
|
8185
8627
|
to: tx.to,
|
|
8186
8628
|
data: tx.data,
|
|
8187
8629
|
value: tx.value ?? "0"
|
|
8630
|
+
}).catch((error) => {
|
|
8631
|
+
if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
|
|
8632
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "The swap transaction was rejected in the wallet.", { outcome: "not-submitted" });
|
|
8633
|
+
throw error;
|
|
8188
8634
|
});
|
|
8189
8635
|
onStage?.("swapped");
|
|
8190
|
-
return { txHash };
|
|
8636
|
+
return { txHash, spent: amount.toString() };
|
|
8191
8637
|
}
|
|
8192
8638
|
async function runSameChainFusion(deps, options, walletAddress) {
|
|
8193
8639
|
const { quote, direction, onStage } = options;
|
|
8194
8640
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8195
|
-
|
|
8196
|
-
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
8641
|
+
let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8642
|
+
const { permit, paid } = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
8643
|
+
amount = await authorisedAmount(deps, options, amount, paid);
|
|
8197
8644
|
onStage?.("building");
|
|
8198
8645
|
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
8199
8646
|
const built = await deps.api.buildOrder({
|
|
@@ -8217,36 +8664,42 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
8217
8664
|
"sign the swap"
|
|
8218
8665
|
);
|
|
8219
8666
|
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8667
|
+
options.assertCurrent?.();
|
|
8668
|
+
options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
|
|
8669
|
+
try {
|
|
8670
|
+
await deps.api.submitOrder({
|
|
8671
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
8672
|
+
// which does not know it and answers with an error that names nothing.
|
|
8673
|
+
rail: "fusion",
|
|
8674
|
+
srcChainId: quote.src.chainId,
|
|
8675
|
+
order: built.order,
|
|
8676
|
+
signature,
|
|
8677
|
+
quoteId: built.quoteId,
|
|
8678
|
+
...built.extension ? { extension: built.extension } : {}
|
|
8679
|
+
});
|
|
8680
|
+
} catch (error) {
|
|
8681
|
+
throw submissionUnknown(error, built.orderHash);
|
|
8682
|
+
}
|
|
8230
8683
|
try {
|
|
8231
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8684
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable: true });
|
|
8232
8685
|
} catch {
|
|
8233
8686
|
}
|
|
8234
8687
|
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
8235
8688
|
orderHash: built.orderHash,
|
|
8236
8689
|
...onStage ? { onStage } : {}
|
|
8237
8690
|
});
|
|
8238
|
-
return { orderHash: built.orderHash };
|
|
8691
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
8239
8692
|
}
|
|
8240
8693
|
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
8241
8694
|
const { quote, onStage } = options;
|
|
8242
|
-
if (!quote.spender || isNativeSource) return
|
|
8695
|
+
if (!quote.spender || isNativeSource) return { paid: false };
|
|
8243
8696
|
const current = await deps.readAllowance(quote.spender);
|
|
8244
8697
|
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
8245
8698
|
spender: quote.spender,
|
|
8246
8699
|
current: current.toString(),
|
|
8247
8700
|
needed: amount.toString()
|
|
8248
8701
|
});
|
|
8249
|
-
if (current >= amount) return
|
|
8702
|
+
if (current >= amount) return { paid: false };
|
|
8250
8703
|
onStage?.("approving");
|
|
8251
8704
|
await deps.ensureChain(quote.src.chainId);
|
|
8252
8705
|
const permit = quote.permitSupported ? await awaitWalletPrompt(
|
|
@@ -8257,11 +8710,18 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
8257
8710
|
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
8258
8711
|
bytes: (permit.length - 2) / 2
|
|
8259
8712
|
});
|
|
8260
|
-
return permit;
|
|
8713
|
+
return { permit, paid: false };
|
|
8261
8714
|
}
|
|
8262
8715
|
await deps.approve(quote.spender, MAX_UINT256);
|
|
8263
8716
|
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
8264
|
-
return
|
|
8717
|
+
return { paid: true };
|
|
8718
|
+
}
|
|
8719
|
+
async function authorisedAmount(deps, options, amount, paid) {
|
|
8720
|
+
if (!paid) return amount;
|
|
8721
|
+
const remaining = await affordableAmount(deps, amount, options.sourceReserve);
|
|
8722
|
+
if (!options.quote.spender || await deps.readAllowance(options.quote.spender) < remaining)
|
|
8723
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed.", { outcome: "not-submitted" });
|
|
8724
|
+
return remaining;
|
|
8265
8725
|
}
|
|
8266
8726
|
async function runFusion(deps, options, walletAddress) {
|
|
8267
8727
|
const { quote, direction, onStage } = options;
|
|
@@ -8273,8 +8733,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8273
8733
|
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
8274
8734
|
);
|
|
8275
8735
|
}
|
|
8276
|
-
|
|
8277
|
-
const permit = await authoriseSpend(deps, options, amount, false);
|
|
8736
|
+
let amount = await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8737
|
+
const { permit, paid } = await authoriseSpend(deps, options, amount, false);
|
|
8738
|
+
amount = await authorisedAmount(deps, options, amount, paid);
|
|
8278
8739
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
8279
8740
|
onStage?.("building");
|
|
8280
8741
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -8293,8 +8754,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8293
8754
|
...direction ? { direction } : {},
|
|
8294
8755
|
...permit ? { permit } : {}
|
|
8295
8756
|
});
|
|
8296
|
-
saveOrder({
|
|
8757
|
+
const durable = saveOrder({
|
|
8297
8758
|
orderHash: built.orderHash,
|
|
8759
|
+
walletAddress,
|
|
8298
8760
|
secrets,
|
|
8299
8761
|
srcChainId: quote.src.chainId,
|
|
8300
8762
|
srcSymbol: quote.src.symbol,
|
|
@@ -8304,33 +8766,39 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8304
8766
|
createdAt: Date.now()
|
|
8305
8767
|
});
|
|
8306
8768
|
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
(
|
|
8314
|
-
|
|
8315
|
-
|
|
8769
|
+
let signature;
|
|
8770
|
+
try {
|
|
8771
|
+
onStage?.("signing");
|
|
8772
|
+
await deps.ensureChain(quote.src.chainId);
|
|
8773
|
+
signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
|
|
8774
|
+
options.assertCurrent?.();
|
|
8775
|
+
options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
|
|
8776
|
+
} catch (error) {
|
|
8777
|
+
clearOrder(built.orderHash);
|
|
8778
|
+
throw error;
|
|
8779
|
+
}
|
|
8316
8780
|
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8781
|
+
try {
|
|
8782
|
+
await deps.api.submitOrder({
|
|
8783
|
+
srcChainId: quote.src.chainId,
|
|
8784
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
8785
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
8786
|
+
order: built.order,
|
|
8787
|
+
signature,
|
|
8788
|
+
quoteId: built.quoteId,
|
|
8789
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
8790
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
8791
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
8792
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
8793
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
8794
|
+
...built.extension ? { extension: built.extension } : {}
|
|
8795
|
+
});
|
|
8796
|
+
} catch (error) {
|
|
8797
|
+
throw submissionUnknown(error, built.orderHash);
|
|
8798
|
+
}
|
|
8331
8799
|
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
8332
8800
|
try {
|
|
8333
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8801
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
|
|
8334
8802
|
} catch {
|
|
8335
8803
|
}
|
|
8336
8804
|
try {
|
|
@@ -8340,13 +8808,25 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8340
8808
|
...onStage ? { onStage } : {}
|
|
8341
8809
|
});
|
|
8342
8810
|
} catch (error) {
|
|
8343
|
-
if (error instanceof OwneyError &&
|
|
8811
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
8344
8812
|
clearOrder(built.orderHash);
|
|
8345
8813
|
}
|
|
8346
8814
|
throw error;
|
|
8347
8815
|
}
|
|
8348
8816
|
clearOrder(built.orderHash);
|
|
8349
|
-
return { orderHash: built.orderHash };
|
|
8817
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
8818
|
+
}
|
|
8819
|
+
function submissionUnknown(error, orderHash) {
|
|
8820
|
+
return new OwneyError(
|
|
8821
|
+
"SWAP_REQUEST_FAILED",
|
|
8822
|
+
"The order submission could not be confirmed. Check this order before starting another swap.",
|
|
8823
|
+
{
|
|
8824
|
+
orderHash,
|
|
8825
|
+
settlementUnconfirmed: true,
|
|
8826
|
+
outcome: "submitted-unknown",
|
|
8827
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
8828
|
+
}
|
|
8829
|
+
);
|
|
8350
8830
|
}
|
|
8351
8831
|
|
|
8352
8832
|
// src/lib/swap/swap.withdrawal-settlement.ts
|
|
@@ -8356,8 +8836,8 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
8356
8836
|
async function awaitWithdrawalPayouts(options) {
|
|
8357
8837
|
const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
|
|
8358
8838
|
const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
|
|
8359
|
-
const deadline = Date.now() + timeoutMs;
|
|
8360
|
-
let
|
|
8839
|
+
const deadline = options.deadline ?? Date.now() + timeoutMs;
|
|
8840
|
+
let active2 = true;
|
|
8361
8841
|
let timer;
|
|
8362
8842
|
const incomplete = (reason, agentId) => new OwneyError(
|
|
8363
8843
|
"WITHDRAW_SWAP_INCOMPLETE",
|
|
@@ -8375,7 +8855,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8375
8855
|
);
|
|
8376
8856
|
const assertActive = () => {
|
|
8377
8857
|
options.assertCurrent();
|
|
8378
|
-
if (!
|
|
8858
|
+
if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
|
|
8379
8859
|
};
|
|
8380
8860
|
const pause = async () => {
|
|
8381
8861
|
assertActive();
|
|
@@ -8386,10 +8866,11 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8386
8866
|
if (evidence.agentId !== id || evidence.chainId !== options.chainId || evidence.token !== options.token || evidence.walletAddress.toLowerCase() !== options.walletAddress.toLowerCase())
|
|
8387
8867
|
throw incomplete("PAYOUT_SCOPE_MISMATCH", id);
|
|
8388
8868
|
};
|
|
8389
|
-
const
|
|
8869
|
+
const record2 = (id, payout, evidence) => {
|
|
8390
8870
|
assertActive();
|
|
8391
8871
|
validate(id, evidence);
|
|
8392
8872
|
payout.evidence = evidence;
|
|
8873
|
+
payout.result.evidence = evidence;
|
|
8393
8874
|
if (evidence.transfer === "confirmed" && evidence.delivery === "confirmed") payout.amount = rawAmount(evidence.confirmedAmount);
|
|
8394
8875
|
options.onProgress(evidence);
|
|
8395
8876
|
};
|
|
@@ -8454,8 +8935,30 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8454
8935
|
if (payout.amount === void 0 || payout.amount <= 0n) throw incomplete("INVALID_PAYOUT_AMOUNT", id);
|
|
8455
8936
|
if (["yieldseeker", "zyfai"].includes(id) && (!hash(evidence.txHash) || rawAmount(evidence.blockNumber) === void 0 || !evidence.logIndexes?.length))
|
|
8456
8937
|
throw incomplete("PAYOUT_RECEIPT_UNVERIFIED", id);
|
|
8457
|
-
if (options.requested === void 0 && payout.result.completion === "unverified")
|
|
8458
|
-
|
|
8938
|
+
if (options.requested === void 0 && payout.result.completion === "unverified") {
|
|
8939
|
+
if (!options.verifyCompletion || id === "yieldseeker" && !evidence.maxCompletion)
|
|
8940
|
+
throw incomplete("MAX_COMPLETION_UNVERIFIED", id);
|
|
8941
|
+
let proof = { status: "unavailable" };
|
|
8942
|
+
try {
|
|
8943
|
+
proof = await bounded(
|
|
8944
|
+
() => options.verifyCompletion(evidence),
|
|
8945
|
+
Math.min(1e4, deadline - Date.now()),
|
|
8946
|
+
() => incomplete("MAX_COMPLETION_UNVERIFIED", id)
|
|
8947
|
+
);
|
|
8948
|
+
} catch {
|
|
8949
|
+
assertActive();
|
|
8950
|
+
}
|
|
8951
|
+
assertActive();
|
|
8952
|
+
evidence.maxCompletionCheck = proof;
|
|
8953
|
+
options.onProgress(evidence);
|
|
8954
|
+
if (proof.status === "remaining") throw incomplete("MAX_REMAINDER_VERIFIED", id);
|
|
8955
|
+
if (proof.status !== "complete") {
|
|
8956
|
+
await pause();
|
|
8957
|
+
continue;
|
|
8958
|
+
}
|
|
8959
|
+
payout.result.completion = void 0;
|
|
8960
|
+
payout.result.type = "full";
|
|
8961
|
+
}
|
|
8459
8962
|
const intended = rawAmount(evidence.intendedAmount);
|
|
8460
8963
|
if (intended !== void 0 && payout.amount < intended && verifiedFee(payout) !== intended - payout.amount)
|
|
8461
8964
|
throw incomplete("PAYOUT_SHORTFALL", id);
|
|
@@ -8465,24 +8968,24 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8465
8968
|
let next = evidence;
|
|
8466
8969
|
if (!next.txHash && (next.userOpHash || next.callsId)) {
|
|
8467
8970
|
next = await options.resolve(next);
|
|
8468
|
-
|
|
8971
|
+
record2(id, payout, next);
|
|
8469
8972
|
}
|
|
8470
8973
|
if (next.transfer === "failed" || next.delivery === "failed") continue;
|
|
8471
8974
|
if (next.txHash) {
|
|
8472
8975
|
next = await options.inspect(next, false);
|
|
8473
|
-
|
|
8976
|
+
record2(id, payout, next);
|
|
8474
8977
|
if (id === "zyfai" && next.transfer === "unknown" && rawAmount(next.blockNumber) !== void 0 && next.logIndexes?.length === 0 && next.transferLogs?.length === 0 && !next.userOpHash && !next.callsId) {
|
|
8475
8978
|
const found = await findPayout(id, next);
|
|
8476
8979
|
if (found) {
|
|
8477
8980
|
next = found;
|
|
8478
|
-
|
|
8981
|
+
record2(id, payout, next);
|
|
8479
8982
|
}
|
|
8480
8983
|
}
|
|
8481
8984
|
} else if (!next.userOpHash && !next.callsId) {
|
|
8482
8985
|
const found = await findPayout(id, next);
|
|
8483
8986
|
if (found) {
|
|
8484
8987
|
next = found;
|
|
8485
|
-
|
|
8988
|
+
record2(id, payout, next);
|
|
8486
8989
|
}
|
|
8487
8990
|
}
|
|
8488
8991
|
if (next.transfer === "confirmed" && next.delivery === "confirmed" || next.transfer === "failed" || next.delivery === "failed") continue;
|
|
@@ -8526,10 +9029,10 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8526
9029
|
};
|
|
8527
9030
|
try {
|
|
8528
9031
|
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
8529
|
-
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")),
|
|
9032
|
+
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
|
|
8530
9033
|
})]);
|
|
8531
9034
|
} finally {
|
|
8532
|
-
|
|
9035
|
+
active2 = false;
|
|
8533
9036
|
if (timer) clearTimeout(timer);
|
|
8534
9037
|
}
|
|
8535
9038
|
}
|
|
@@ -8550,10 +9053,10 @@ function verifiedFee(payout) {
|
|
|
8550
9053
|
import {
|
|
8551
9054
|
domainSeparator,
|
|
8552
9055
|
encodeAbiParameters,
|
|
8553
|
-
parseAbi as
|
|
9056
|
+
parseAbi as parseAbi5,
|
|
8554
9057
|
parseSignature
|
|
8555
9058
|
} from "viem";
|
|
8556
|
-
var PERMIT_ABI =
|
|
9059
|
+
var PERMIT_ABI = parseAbi5([
|
|
8557
9060
|
"function nonces(address owner) view returns (uint256)",
|
|
8558
9061
|
"function name() view returns (string)",
|
|
8559
9062
|
"function version() view returns (string)",
|
|
@@ -8651,7 +9154,7 @@ async function buildErc2612Permit(reads, input) {
|
|
|
8651
9154
|
|
|
8652
9155
|
// src/lib/swap/swap.withdraw-swap.ts
|
|
8653
9156
|
var KEY_PREFIX5 = "owney.swap.withdraw";
|
|
8654
|
-
var
|
|
9157
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
8655
9158
|
var storage5 = () => {
|
|
8656
9159
|
if (typeof window === "undefined") return null;
|
|
8657
9160
|
try {
|
|
@@ -8676,15 +9179,19 @@ var notify = () => {
|
|
|
8676
9179
|
}
|
|
8677
9180
|
}
|
|
8678
9181
|
};
|
|
8679
|
-
function saveWithdrawSwap(
|
|
9182
|
+
function saveWithdrawSwap(record2) {
|
|
8680
9183
|
const store = storage5();
|
|
8681
|
-
if (!store) return;
|
|
9184
|
+
if (!store) return false;
|
|
8682
9185
|
try {
|
|
8683
|
-
store.
|
|
9186
|
+
const current = store.getItem(keyFor3(record2.orderHash));
|
|
9187
|
+
const value = JSON.stringify({ ...current ? JSON.parse(current) : {}, ...record2 });
|
|
9188
|
+
store.setItem(keyFor3(record2.orderHash), value);
|
|
9189
|
+
if (store.getItem(keyFor3(record2.orderHash)) !== value) return false;
|
|
8684
9190
|
} catch {
|
|
8685
|
-
return;
|
|
9191
|
+
return false;
|
|
8686
9192
|
}
|
|
8687
9193
|
notify();
|
|
9194
|
+
return true;
|
|
8688
9195
|
}
|
|
8689
9196
|
function updateWithdrawSwap(orderHash, patch) {
|
|
8690
9197
|
const store = storage5();
|
|
@@ -8707,13 +9214,17 @@ function clearWithdrawSwap(orderHash) {
|
|
|
8707
9214
|
const store = storage5();
|
|
8708
9215
|
if (!store) return;
|
|
8709
9216
|
try {
|
|
8710
|
-
store.
|
|
9217
|
+
const raw2 = store.getItem(keyFor3(orderHash));
|
|
9218
|
+
const record2 = raw2 ? JSON.parse(raw2) : void 0;
|
|
9219
|
+
if (record2 && !["landed", "refunded", "not-swapped"].includes(record2.status))
|
|
9220
|
+
store.setItem(keyFor3(orderHash), JSON.stringify({ ...record2, dismissed: true }));
|
|
9221
|
+
else store.removeItem(keyFor3(orderHash));
|
|
8711
9222
|
} catch {
|
|
8712
9223
|
return;
|
|
8713
9224
|
}
|
|
8714
9225
|
notify();
|
|
8715
9226
|
}
|
|
8716
|
-
function listWithdrawSwaps(now = Date.now()) {
|
|
9227
|
+
function listWithdrawSwaps(now = Date.now(), includeDismissed = false) {
|
|
8717
9228
|
const store = storage5();
|
|
8718
9229
|
if (!store) return [];
|
|
8719
9230
|
const out = [];
|
|
@@ -8728,11 +9239,11 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8728
9239
|
if (!raw2) continue;
|
|
8729
9240
|
try {
|
|
8730
9241
|
const parsed = JSON.parse(raw2);
|
|
8731
|
-
if (now - parsed.createdAt >
|
|
9242
|
+
if (["landed", "refunded", "not-swapped"].includes(parsed.status) && now - (parsed.settledAt ?? parsed.createdAt) > MAX_AGE_MS) {
|
|
8732
9243
|
store.removeItem(key2);
|
|
8733
9244
|
continue;
|
|
8734
9245
|
}
|
|
8735
|
-
out.push(parsed);
|
|
9246
|
+
if (includeDismissed || !parsed.dismissed) out.push(parsed);
|
|
8736
9247
|
} catch {
|
|
8737
9248
|
store.removeItem(key2);
|
|
8738
9249
|
}
|
|
@@ -8742,10 +9253,15 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8742
9253
|
}
|
|
8743
9254
|
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
8744
9255
|
}
|
|
9256
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
9257
|
+
window.addEventListener("storage", (event) => {
|
|
9258
|
+
if (event.key === null || event.key.startsWith(`${KEY_PREFIX5}.`)) notify();
|
|
9259
|
+
});
|
|
9260
|
+
}
|
|
8745
9261
|
|
|
8746
9262
|
// src/lib/swap/swap.pending-deposit.ts
|
|
8747
9263
|
var KEY_PREFIX6 = "owney.swap.pending-deposit";
|
|
8748
|
-
var
|
|
9264
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
8749
9265
|
var storage6 = () => {
|
|
8750
9266
|
if (typeof window === "undefined") return null;
|
|
8751
9267
|
try {
|
|
@@ -8788,7 +9304,7 @@ function listPendingDeposits(now = Date.now()) {
|
|
|
8788
9304
|
if (!raw2) continue;
|
|
8789
9305
|
try {
|
|
8790
9306
|
const parsed = JSON.parse(raw2);
|
|
8791
|
-
if (now - parsed.createdAt >
|
|
9307
|
+
if (now - parsed.createdAt > MAX_AGE_MS2) {
|
|
8792
9308
|
store.removeItem(key2);
|
|
8793
9309
|
continue;
|
|
8794
9310
|
}
|
|
@@ -8837,7 +9353,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
8837
9353
|
}
|
|
8838
9354
|
|
|
8839
9355
|
// src/lib/helpers/withdraw-helper.ts
|
|
8840
|
-
import { parseUnits as
|
|
9356
|
+
import { parseUnits as parseUnits3 } from "viem";
|
|
8841
9357
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
8842
9358
|
return agents.map((agent) => {
|
|
8843
9359
|
let invalidBalance = false;
|
|
@@ -8847,7 +9363,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
8847
9363
|
return 0n;
|
|
8848
9364
|
}
|
|
8849
9365
|
const [whole, fraction] = value.split(".");
|
|
8850
|
-
return raw2 ? BigInt(value) :
|
|
9366
|
+
return raw2 ? BigInt(value) : parseUnits3(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
|
|
8851
9367
|
};
|
|
8852
9368
|
const agentBalance = aggregated[agent.id];
|
|
8853
9369
|
const tokenBalance = agentBalance?.tokens.find(
|
|
@@ -9047,11 +9563,11 @@ function aggregateDailyEarnings(series, tokenSymbol) {
|
|
|
9047
9563
|
const byAsset = /* @__PURE__ */ new Map();
|
|
9048
9564
|
for (const account of series) {
|
|
9049
9565
|
for (const { asset, points } of account.assets) {
|
|
9050
|
-
const
|
|
9051
|
-
if (!points.length || wanted &&
|
|
9052
|
-
const components = byAsset.get(
|
|
9566
|
+
const symbol2 = asset.toUpperCase();
|
|
9567
|
+
if (!points.length || wanted && symbol2 !== wanted) continue;
|
|
9568
|
+
const components = byAsset.get(symbol2) ?? [];
|
|
9053
9569
|
components.push([...points].sort((a, b) => a.date.localeCompare(b.date)));
|
|
9054
|
-
byAsset.set(
|
|
9570
|
+
byAsset.set(symbol2, components);
|
|
9055
9571
|
}
|
|
9056
9572
|
}
|
|
9057
9573
|
return [...byAsset.entries()].map(([asset, components]) => {
|
|
@@ -9080,7 +9596,7 @@ function aggregateDailyEarnings(series, tokenSymbol) {
|
|
|
9080
9596
|
|
|
9081
9597
|
// src/client.ts
|
|
9082
9598
|
import {
|
|
9083
|
-
parseUnits as
|
|
9599
|
+
parseUnits as parseUnits4,
|
|
9084
9600
|
createPublicClient as createPublicClient4,
|
|
9085
9601
|
createWalletClient as createWalletClient5,
|
|
9086
9602
|
custom as custom5,
|
|
@@ -9096,111 +9612,6 @@ import {
|
|
|
9096
9612
|
parseTransaction,
|
|
9097
9613
|
toBytes
|
|
9098
9614
|
} from "viem";
|
|
9099
|
-
|
|
9100
|
-
// src/lib/permit2-batch.ts
|
|
9101
|
-
import { parseAbi as parseAbi5, hashStruct } from "viem";
|
|
9102
|
-
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
9103
|
-
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
9104
|
-
var ERC2612_PERMIT_TYPES = {
|
|
9105
|
-
Permit: [
|
|
9106
|
-
{ name: "owner", type: "address" },
|
|
9107
|
-
{ name: "spender", type: "address" },
|
|
9108
|
-
{ name: "value", type: "uint256" },
|
|
9109
|
-
{ name: "nonce", type: "uint256" },
|
|
9110
|
-
{ name: "deadline", type: "uint256" }
|
|
9111
|
-
]
|
|
9112
|
-
};
|
|
9113
|
-
var ERC2612_READ_ABI = parseAbi5([
|
|
9114
|
-
"function name() view returns (string)",
|
|
9115
|
-
"function version() view returns (string)",
|
|
9116
|
-
"function nonces(address owner) view returns (uint256)"
|
|
9117
|
-
]);
|
|
9118
|
-
function erc2612TypedData(input) {
|
|
9119
|
-
return {
|
|
9120
|
-
domain: {
|
|
9121
|
-
name: input.permit.tokenName,
|
|
9122
|
-
version: input.permit.tokenVersion,
|
|
9123
|
-
chainId: input.chainId,
|
|
9124
|
-
verifyingContract: input.token
|
|
9125
|
-
},
|
|
9126
|
-
types: ERC2612_PERMIT_TYPES,
|
|
9127
|
-
primaryType: "Permit",
|
|
9128
|
-
message: {
|
|
9129
|
-
owner: input.owner,
|
|
9130
|
-
spender: BATCH_PERMIT2_ADDRESS,
|
|
9131
|
-
value: BigInt(input.permit.value),
|
|
9132
|
-
nonce: BigInt(input.permit.nonce),
|
|
9133
|
-
deadline: BigInt(input.permit.deadline)
|
|
9134
|
-
}
|
|
9135
|
-
};
|
|
9136
|
-
}
|
|
9137
|
-
var PERMIT_BATCH_TYPES = {
|
|
9138
|
-
PermitBatchWitnessTransferFrom: [
|
|
9139
|
-
{ name: "permitted", type: "TokenPermissions[]" },
|
|
9140
|
-
{ name: "spender", type: "address" },
|
|
9141
|
-
{ name: "nonce", type: "uint256" },
|
|
9142
|
-
{ name: "deadline", type: "uint256" },
|
|
9143
|
-
{ name: "witness", type: "Deposit" }
|
|
9144
|
-
],
|
|
9145
|
-
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
9146
|
-
TokenPermissions: [
|
|
9147
|
-
{ name: "token", type: "address" },
|
|
9148
|
-
{ name: "amount", type: "uint256" }
|
|
9149
|
-
]
|
|
9150
|
-
};
|
|
9151
|
-
var PERMIT2_BATCH_ABI = parseAbi5([
|
|
9152
|
-
"struct TokenPermissions { address token; uint256 amount; }",
|
|
9153
|
-
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
9154
|
-
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
9155
|
-
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
9156
|
-
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
9157
|
-
]);
|
|
9158
|
-
function batchPermit(b) {
|
|
9159
|
-
return {
|
|
9160
|
-
permitted: b.transfers.map((t) => ({
|
|
9161
|
-
token: b.token,
|
|
9162
|
-
amount: BigInt(t.amount)
|
|
9163
|
-
})),
|
|
9164
|
-
nonce: BigInt(b.nonce),
|
|
9165
|
-
deadline: BigInt(b.deadline)
|
|
9166
|
-
};
|
|
9167
|
-
}
|
|
9168
|
-
function batchTypedData(b, spender) {
|
|
9169
|
-
return {
|
|
9170
|
-
domain: {
|
|
9171
|
-
name: "Permit2",
|
|
9172
|
-
chainId: b.chainId,
|
|
9173
|
-
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
9174
|
-
},
|
|
9175
|
-
types: PERMIT_BATCH_TYPES,
|
|
9176
|
-
primaryType: "PermitBatchWitnessTransferFrom",
|
|
9177
|
-
message: {
|
|
9178
|
-
...batchPermit(b),
|
|
9179
|
-
spender,
|
|
9180
|
-
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
9181
|
-
}
|
|
9182
|
-
};
|
|
9183
|
-
}
|
|
9184
|
-
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
9185
|
-
function batchWitness(b) {
|
|
9186
|
-
return hashStruct({
|
|
9187
|
-
types: PERMIT_BATCH_TYPES,
|
|
9188
|
-
primaryType: "Deposit",
|
|
9189
|
-
data: { recipients: b.transfers.map((t) => t.to) }
|
|
9190
|
-
});
|
|
9191
|
-
}
|
|
9192
|
-
function batchArgs(b) {
|
|
9193
|
-
return [
|
|
9194
|
-
batchPermit(b),
|
|
9195
|
-
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
9196
|
-
b.from,
|
|
9197
|
-
batchWitness(b),
|
|
9198
|
-
BATCH_WITNESS_TYPE,
|
|
9199
|
-
b.signature
|
|
9200
|
-
];
|
|
9201
|
-
}
|
|
9202
|
-
|
|
9203
|
-
// src/lib/sponsored-token-batch.ts
|
|
9204
9615
|
var memory = /* @__PURE__ */ new Map();
|
|
9205
9616
|
var settledRecords = /* @__PURE__ */ new Map();
|
|
9206
9617
|
var expiredRecords = /* @__PURE__ */ new Map();
|
|
@@ -9228,14 +9639,14 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9228
9639
|
let timer;
|
|
9229
9640
|
let expired = false;
|
|
9230
9641
|
const check = async () => {
|
|
9231
|
-
let
|
|
9232
|
-
if (
|
|
9233
|
-
const current = JSON.parse(
|
|
9642
|
+
let active2 = read(key2);
|
|
9643
|
+
if (active2 && i.expectedTxHash) {
|
|
9644
|
+
const current = JSON.parse(active2);
|
|
9234
9645
|
if (!current.serializedTransaction || keccak2562(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
|
|
9235
|
-
|
|
9646
|
+
active2 = void 0;
|
|
9236
9647
|
}
|
|
9237
9648
|
const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
|
|
9238
|
-
const saved =
|
|
9649
|
+
const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
|
|
9239
9650
|
if (!saved) return;
|
|
9240
9651
|
const body = JSON.parse(saved);
|
|
9241
9652
|
if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
|
|
@@ -9259,8 +9670,8 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9259
9670
|
args: [i.owner, nonce >> 8n],
|
|
9260
9671
|
blockNumber: block.number
|
|
9261
9672
|
});
|
|
9262
|
-
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n ||
|
|
9263
|
-
if (
|
|
9673
|
+
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
|
|
9674
|
+
if (active2) {
|
|
9264
9675
|
const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
|
|
9265
9676
|
expiredRecords.set(terminalKey, saved);
|
|
9266
9677
|
let persisted = typeof window === "undefined";
|
|
@@ -9318,15 +9729,15 @@ function clear(key2, body) {
|
|
|
9318
9729
|
function sponsorTokenBatch(i) {
|
|
9319
9730
|
const key2 = pendingKey(i);
|
|
9320
9731
|
const plan = planOf(i.transfers);
|
|
9321
|
-
const
|
|
9322
|
-
if (
|
|
9323
|
-
if (
|
|
9732
|
+
const active2 = inflight.get(key2);
|
|
9733
|
+
if (active2) {
|
|
9734
|
+
if (active2.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
9324
9735
|
return Promise.reject(
|
|
9325
9736
|
new Error(
|
|
9326
9737
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
9327
9738
|
)
|
|
9328
9739
|
);
|
|
9329
|
-
return
|
|
9740
|
+
return active2.promise;
|
|
9330
9741
|
}
|
|
9331
9742
|
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
9332
9743
|
inflight.set(key2, { plan, promise });
|
|
@@ -9480,7 +9891,7 @@ async function execute(i, key2, plan) {
|
|
|
9480
9891
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
9481
9892
|
"Insufficient token balance for this deposit."
|
|
9482
9893
|
);
|
|
9483
|
-
if (allowance < total && (i.chainId
|
|
9894
|
+
if (allowance < total && !supportsSponsoredErc2612(i.chainId, i.token))
|
|
9484
9895
|
throw new OwneyError(
|
|
9485
9896
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
9486
9897
|
"token deposits need a one-time Permit2 approval."
|
|
@@ -9516,7 +9927,7 @@ async function execute(i, key2, plan) {
|
|
|
9516
9927
|
const signature2 = await i.wallet.signTypedData({
|
|
9517
9928
|
account: i.owner,
|
|
9518
9929
|
...erc2612TypedData({
|
|
9519
|
-
chainId:
|
|
9930
|
+
chainId: i.chainId,
|
|
9520
9931
|
token: i.token,
|
|
9521
9932
|
owner: i.owner,
|
|
9522
9933
|
permit: unsignedPermit
|
|
@@ -10144,12 +10555,12 @@ var OwneySDK = class {
|
|
|
10144
10555
|
return [...this.getActiveAgents().keys()].sort();
|
|
10145
10556
|
}
|
|
10146
10557
|
getActiveAgents() {
|
|
10147
|
-
const
|
|
10558
|
+
const active2 = /* @__PURE__ */ new Map();
|
|
10148
10559
|
for (const id of this.activeAgents) {
|
|
10149
10560
|
const agent = this.agents.get(id);
|
|
10150
|
-
if (agent)
|
|
10561
|
+
if (agent) active2.set(id, agent);
|
|
10151
10562
|
}
|
|
10152
|
-
return
|
|
10563
|
+
return active2;
|
|
10153
10564
|
}
|
|
10154
10565
|
/**
|
|
10155
10566
|
* Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
|
|
@@ -10732,10 +11143,10 @@ var OwneySDK = class {
|
|
|
10732
11143
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
10733
11144
|
* two independent auto-recovery mechanisms:
|
|
10734
11145
|
*
|
|
10735
|
-
* 1. Missing Permit2 allowance outside the
|
|
11146
|
+
* 1. Missing Permit2 allowance outside the sponsored ERC-2612 path: when the
|
|
10736
11147
|
* app did not supply its own callback and the attempt fails with
|
|
10737
11148
|
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
10738
|
-
* for that token. Base USDC
|
|
11149
|
+
* for that token. Native Base/Arbitrum USDC uses gasless ERC-2612 approval in
|
|
10739
11150
|
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
10740
11151
|
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
10741
11152
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
@@ -11242,8 +11653,8 @@ var OwneySDK = class {
|
|
|
11242
11653
|
async ensureSwapChain(chainId) {
|
|
11243
11654
|
const provider = this.requireConnectedProvider();
|
|
11244
11655
|
const state = this.requireState();
|
|
11245
|
-
const
|
|
11246
|
-
if (!
|
|
11656
|
+
const chain2 = swapChain(chainId);
|
|
11657
|
+
if (!chain2) {
|
|
11247
11658
|
throw new OwneyError(
|
|
11248
11659
|
"CHAIN_UNSUPPORTED",
|
|
11249
11660
|
`Chain ${chainId} is not supported`,
|
|
@@ -11251,10 +11662,10 @@ var OwneySDK = class {
|
|
|
11251
11662
|
);
|
|
11252
11663
|
}
|
|
11253
11664
|
await ensureWalletOnChain(
|
|
11254
|
-
createPublicClient4({ chain, transport: custom5(provider) }),
|
|
11665
|
+
createPublicClient4({ chain: chain2, transport: custom5(provider) }),
|
|
11255
11666
|
createWalletClient5({
|
|
11256
11667
|
account: state.walletAddress,
|
|
11257
|
-
chain,
|
|
11668
|
+
chain: chain2,
|
|
11258
11669
|
transport: custom5(provider)
|
|
11259
11670
|
}),
|
|
11260
11671
|
chainId
|
|
@@ -11278,15 +11689,15 @@ var OwneySDK = class {
|
|
|
11278
11689
|
async wrapNativeSource(quote, walletAddress, onStage, direction) {
|
|
11279
11690
|
const wrapper = requireWrapper(quote);
|
|
11280
11691
|
const chainId = quote.src.chainId;
|
|
11281
|
-
const
|
|
11692
|
+
const chain2 = swapChain(chainId);
|
|
11282
11693
|
const provider = this.requireConnectedProvider();
|
|
11283
11694
|
const publicClient = createPublicClient4({
|
|
11284
|
-
chain,
|
|
11695
|
+
chain: chain2,
|
|
11285
11696
|
transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
|
|
11286
11697
|
});
|
|
11287
11698
|
const wallet = createWalletClient5({
|
|
11288
11699
|
account: walletAddress,
|
|
11289
|
-
chain,
|
|
11700
|
+
chain: chain2,
|
|
11290
11701
|
transport: custom5(provider)
|
|
11291
11702
|
});
|
|
11292
11703
|
onStage?.("wrapping");
|
|
@@ -11313,7 +11724,7 @@ var OwneySDK = class {
|
|
|
11313
11724
|
data: tx.data,
|
|
11314
11725
|
value: BigInt(tx.value),
|
|
11315
11726
|
account: walletAddress,
|
|
11316
|
-
chain
|
|
11727
|
+
chain: chain2
|
|
11317
11728
|
});
|
|
11318
11729
|
onSubmitted?.(hash2);
|
|
11319
11730
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
@@ -11383,15 +11794,27 @@ var OwneySDK = class {
|
|
|
11383
11794
|
* Kept as a builder rather than baked into the executor so the whole swap
|
|
11384
11795
|
* flow stays testable without a provider — the executor never imports viem.
|
|
11385
11796
|
*/
|
|
11386
|
-
buildSwapDeps(quote) {
|
|
11797
|
+
buildSwapDeps(quote, assertOperation, onTransactionHash) {
|
|
11387
11798
|
const state = this.requireState();
|
|
11388
11799
|
const provider = this.requireConnectedProvider();
|
|
11800
|
+
const revision = this.connectionRevision;
|
|
11801
|
+
const assertWallet = () => {
|
|
11802
|
+
assertOperation?.();
|
|
11803
|
+
if (this.state !== state || this.connectionRevision !== revision)
|
|
11804
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed before swap authorization.");
|
|
11805
|
+
};
|
|
11389
11806
|
const srcChain = swapChain(quote.src.chainId);
|
|
11390
11807
|
const dstChain = swapChain(quote.dst.chainId);
|
|
11391
11808
|
const wallet = createWalletClient5({
|
|
11392
11809
|
account: state.walletAddress,
|
|
11393
11810
|
chain: srcChain,
|
|
11394
|
-
|
|
11811
|
+
// viem can await chain/account preparation before dispatching a wallet
|
|
11812
|
+
// request. Recheck at the transport boundary so an expired attempt or
|
|
11813
|
+
// replaced session cannot send after that internal await completes.
|
|
11814
|
+
transport: custom5({ request: (args) => {
|
|
11815
|
+
assertWallet();
|
|
11816
|
+
return provider.request(args);
|
|
11817
|
+
} }, { retryCount: 0 })
|
|
11395
11818
|
});
|
|
11396
11819
|
const srcPublic = createPublicClient4({
|
|
11397
11820
|
chain: srcChain,
|
|
@@ -11420,6 +11843,7 @@ var OwneySDK = class {
|
|
|
11420
11843
|
});
|
|
11421
11844
|
},
|
|
11422
11845
|
sendTransaction: async (tx) => {
|
|
11846
|
+
assertWallet();
|
|
11423
11847
|
const hash2 = await wallet.sendTransaction({
|
|
11424
11848
|
to: tx.to,
|
|
11425
11849
|
data: tx.data,
|
|
@@ -11427,24 +11851,34 @@ var OwneySDK = class {
|
|
|
11427
11851
|
account: state.walletAddress,
|
|
11428
11852
|
chain: srcChain
|
|
11429
11853
|
});
|
|
11854
|
+
onTransactionHash?.(hash2);
|
|
11430
11855
|
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11431
11856
|
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
11432
11857
|
hash: hash2,
|
|
11433
11858
|
confirmations: 1
|
|
11859
|
+
}).catch(() => {
|
|
11860
|
+
throw new OwneyError(
|
|
11861
|
+
"SWAP_REQUEST_FAILED",
|
|
11862
|
+
"Swap receipt is not confirmed yet.",
|
|
11863
|
+
{ txHash: hash2, settlementUnconfirmed: true, outcome: "submitted-unknown" }
|
|
11864
|
+
);
|
|
11434
11865
|
});
|
|
11435
11866
|
if (receipt.status !== "success") {
|
|
11436
11867
|
throw new OwneyError(
|
|
11437
11868
|
"SWAP_REQUEST_FAILED",
|
|
11438
11869
|
`Swap transaction reverted (tx ${hash2})`,
|
|
11439
|
-
{
|
|
11870
|
+
{ txHash: hash2, outcome: "not-submitted", executionFailed: true }
|
|
11440
11871
|
);
|
|
11441
11872
|
}
|
|
11442
11873
|
return hash2;
|
|
11443
11874
|
},
|
|
11444
|
-
signTypedData: (typedData) =>
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11875
|
+
signTypedData: (typedData) => {
|
|
11876
|
+
assertWallet();
|
|
11877
|
+
return wallet.signTypedData({
|
|
11878
|
+
account: state.walletAddress,
|
|
11879
|
+
...typedData
|
|
11880
|
+
});
|
|
11881
|
+
},
|
|
11448
11882
|
// Chain-bound like every other read here: the wallet provider's chain is
|
|
11449
11883
|
// not ours to rely on mid-swap.
|
|
11450
11884
|
readSourceBalance: async () => {
|
|
@@ -11466,6 +11900,7 @@ var OwneySDK = class {
|
|
|
11466
11900
|
args: [state.walletAddress, spender]
|
|
11467
11901
|
}),
|
|
11468
11902
|
approve: async (spender, amount) => {
|
|
11903
|
+
assertWallet();
|
|
11469
11904
|
const hash2 = await wallet.writeContract({
|
|
11470
11905
|
address: quote.src.address,
|
|
11471
11906
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -11474,14 +11909,22 @@ var OwneySDK = class {
|
|
|
11474
11909
|
account: state.walletAddress,
|
|
11475
11910
|
chain: srcChain
|
|
11476
11911
|
});
|
|
11477
|
-
await srcPublic.waitForTransactionReceipt({
|
|
11912
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11478
11913
|
hash: hash2,
|
|
11479
11914
|
confirmations: 1,
|
|
11480
11915
|
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
11481
11916
|
});
|
|
11917
|
+
if (receipt.status !== "success") throw new OwneyError(
|
|
11918
|
+
"SWAP_REQUEST_FAILED",
|
|
11919
|
+
"Swap approval reverted.",
|
|
11920
|
+
{ hash: hash2, phase: "approval", outcome: "not-submitted" }
|
|
11921
|
+
);
|
|
11482
11922
|
return hash2;
|
|
11483
11923
|
},
|
|
11484
|
-
ensureChain: (chainId) =>
|
|
11924
|
+
ensureChain: (chainId) => {
|
|
11925
|
+
assertWallet();
|
|
11926
|
+
return this.ensureSwapChain(chainId);
|
|
11927
|
+
},
|
|
11485
11928
|
/**
|
|
11486
11929
|
* Sign the approval instead of paying for it. Null means the token
|
|
11487
11930
|
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
@@ -11501,10 +11944,13 @@ var OwneySDK = class {
|
|
|
11501
11944
|
spender,
|
|
11502
11945
|
value: amount,
|
|
11503
11946
|
chainId: quote.src.chainId,
|
|
11504
|
-
signTypedData: (payload) =>
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11947
|
+
signTypedData: (payload) => {
|
|
11948
|
+
assertWallet();
|
|
11949
|
+
return wallet.signTypedData({
|
|
11950
|
+
account: state.walletAddress,
|
|
11951
|
+
...payload
|
|
11952
|
+
});
|
|
11953
|
+
}
|
|
11508
11954
|
}
|
|
11509
11955
|
),
|
|
11510
11956
|
/**
|
|
@@ -11554,36 +12000,20 @@ var OwneySDK = class {
|
|
|
11554
12000
|
);
|
|
11555
12001
|
}
|
|
11556
12002
|
/**
|
|
11557
|
-
*
|
|
11558
|
-
*
|
|
11559
|
-
*
|
|
11560
|
-
*
|
|
11561
|
-
*
|
|
11562
|
-
*
|
|
11563
|
-
* Something does. A first WETH deposit needs a one-time Permit2 approval,
|
|
11564
|
-
* and that approval is a transaction. A wallet holding no native gas token -
|
|
11565
|
-
* the normal state of someone who just swapped everything INTO WETH - pays
|
|
11566
|
-
* for it out of the WETH. The deposit then asked for ~2e12 wei more than was
|
|
11567
|
-
* left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
|
|
11568
|
-
* the funds were safe but unadded (ROUT-242).
|
|
11569
|
-
*
|
|
11570
|
-
* So the approval happens here, before the amount is settled, and the amount
|
|
11571
|
-
* comes from a read taken after it. Two things this deliberately is not:
|
|
11572
|
-
*
|
|
11573
|
-
* - It is not a balance sweep. `received` is a DELTA over the pre-swap
|
|
11574
|
-
* balance, so a wallet already holding WETH reads far above it; depositing
|
|
11575
|
-
* the balance would move funds the user never offered. The arrival is the
|
|
11576
|
-
* ceiling and the balance is only ever allowed to lower it.
|
|
11577
|
-
* - It does not touch USDC, which moves on signed typed data and so spends
|
|
11578
|
-
* nothing of the user's in between, nor a paymaster-configured client,
|
|
11579
|
-
* where the sponsor covers the transfer and Permit2 never applies.
|
|
12003
|
+
* Finish any user-paid Permit2 approval before measuring the deposit.
|
|
12004
|
+
* Wallets can charge approval gas in the received token (ROUT-242/544).
|
|
12005
|
+
* Cap the result at the swap arrival to preserve pre-existing wallet funds.
|
|
12006
|
+
* Native Base/Arbitrum USDC uses sponsored ERC-2612; configured paymaster
|
|
12007
|
+
* wallet calls also cover approval without spending the received tokens.
|
|
11580
12008
|
*/
|
|
11581
|
-
async settleSwapDepositAmount(chainId, token,
|
|
11582
|
-
if (
|
|
12009
|
+
async settleSwapDepositAmount(chainId, token, symbol2, received) {
|
|
12010
|
+
if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
|
|
11583
12011
|
return received.toString();
|
|
11584
12012
|
}
|
|
11585
12013
|
const allowance = await this.readTokenPermit2Allowance(chainId, token);
|
|
11586
|
-
if (allowance < received)
|
|
12014
|
+
if (allowance < received) {
|
|
12015
|
+
await this.approvePermit2(symbol2, received, chainId);
|
|
12016
|
+
}
|
|
11587
12017
|
const balance = await this.readTokenBalance(chainId, token);
|
|
11588
12018
|
return (balance < received ? balance : received).toString();
|
|
11589
12019
|
}
|
|
@@ -11710,21 +12140,30 @@ var OwneySDK = class {
|
|
|
11710
12140
|
* Every order is attempted even if an earlier one throws: they are unrelated
|
|
11711
12141
|
* escrows, and one ending badly must not abandon the next one's secrets.
|
|
11712
12142
|
* `unfinished` is not a verdict — the order may still settle, so unless the
|
|
11713
|
-
*
|
|
12143
|
+
* provider has confirmed full settlement or refund, its preimages are kept.
|
|
11714
12144
|
*/
|
|
11715
12145
|
async resumePendingSwaps(options = {}) {
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
12146
|
+
await mapConcurrent(listWithdrawSwaps(Date.now(), true).filter((record2) => record2.txHash && record2.status === "unknown" && (!options.orderHash || options.orderHash === record2.orderHash)), async (record2) => {
|
|
12147
|
+
try {
|
|
12148
|
+
const rpc2 = createPublicClient4({ chain: swapChain(record2.srcChainId), transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls) });
|
|
12149
|
+
const receipt = await bounded(
|
|
12150
|
+
() => rpc2.getTransactionReceipt({ hash: record2.txHash }),
|
|
12151
|
+
8e3,
|
|
12152
|
+
() => new Error("Receipt unavailable")
|
|
12153
|
+
);
|
|
12154
|
+
updateWithdrawSwap(record2.orderHash, { status: receipt.status === "success" ? "landed" : "not-swapped" });
|
|
12155
|
+
} catch {
|
|
12156
|
+
}
|
|
12157
|
+
});
|
|
12158
|
+
const pending = listOrders().filter((order) => !isOrderObserved(order.orderHash) && (!options.orderHash || order.orderHash === options.orderHash));
|
|
11721
12159
|
const runner = this.swapRunnerDeps();
|
|
11722
|
-
const
|
|
11723
|
-
|
|
12160
|
+
const sameChain = this.resumeSameChainSwaps(options);
|
|
12161
|
+
const results = await mapConcurrent(pending, async (order) => {
|
|
11724
12162
|
try {
|
|
11725
12163
|
await runFusionOrder(runner, {
|
|
11726
12164
|
orderHash: order.orderHash,
|
|
11727
12165
|
secrets: order.secrets,
|
|
12166
|
+
hasPartialFill: listWithdrawSwaps(Date.now(), true).some((record2) => record2.orderHash === order.orderHash && record2.status === "partially-filled"),
|
|
11728
12167
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11729
12168
|
...options.onStage ? {
|
|
11730
12169
|
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
@@ -11732,25 +12171,23 @@ var OwneySDK = class {
|
|
|
11732
12171
|
});
|
|
11733
12172
|
clearOrder(order.orderHash);
|
|
11734
12173
|
updateWithdrawSwap(order.orderHash, { status: "landed" });
|
|
11735
|
-
|
|
11736
|
-
orderHash: order.orderHash,
|
|
11737
|
-
status: "completed"
|
|
11738
|
-
});
|
|
12174
|
+
return { orderHash: order.orderHash, status: "completed" };
|
|
11739
12175
|
} catch (error) {
|
|
11740
|
-
if (error instanceof OwneyError &&
|
|
12176
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
11741
12177
|
clearOrder(order.orderHash);
|
|
11742
12178
|
updateWithdrawSwap(order.orderHash, { status: "refunded" });
|
|
11743
12179
|
} else {
|
|
11744
|
-
|
|
12180
|
+
if (!(error instanceof OwneyError && error.details?.watchActive))
|
|
12181
|
+
updateWithdrawSwap(order.orderHash, { status: error instanceof OwneyError && error.details?.outcome === "refund-pending" ? "refund-pending" : error instanceof OwneyError && error.details?.outcome === "partially-filled" ? "partially-filled" : "unknown" });
|
|
11745
12182
|
}
|
|
11746
|
-
|
|
12183
|
+
return {
|
|
11747
12184
|
orderHash: order.orderHash,
|
|
11748
12185
|
status: "unfinished",
|
|
11749
12186
|
...error instanceof OwneyError ? { error } : {}
|
|
11750
|
-
}
|
|
12187
|
+
};
|
|
11751
12188
|
}
|
|
11752
|
-
}
|
|
11753
|
-
await
|
|
12189
|
+
});
|
|
12190
|
+
await sameChain;
|
|
11754
12191
|
return results;
|
|
11755
12192
|
}
|
|
11756
12193
|
/**
|
|
@@ -11771,34 +12208,32 @@ var OwneySDK = class {
|
|
|
11771
12208
|
* anything else.
|
|
11772
12209
|
*/
|
|
11773
12210
|
async resumeSameChainSwaps(options = {}) {
|
|
11774
|
-
const running = listWithdrawSwaps().filter(
|
|
11775
|
-
|
|
11776
|
-
);
|
|
11777
|
-
for (const record of running) {
|
|
12211
|
+
const running = listWithdrawSwaps(Date.now(), true).filter((record2) => ["swapping", "unknown", "partially-filled", "refund-pending"].includes(record2.status) && record2.srcChainId === record2.dstChainId && /^0x[0-9a-f]{64}$/i.test(record2.orderHash) && !isOrderObserved(record2.orderHash) && (!options.orderHash || record2.orderHash === options.orderHash));
|
|
12212
|
+
await mapConcurrent(running, async (record2) => {
|
|
11778
12213
|
const runner = {
|
|
11779
12214
|
// The chain bound in. That product keys its orders per chain, and the
|
|
11780
12215
|
// Fusion+ endpoint answers 404 for one — which would read as an order
|
|
11781
12216
|
// that vanished rather than one still in the auction.
|
|
11782
|
-
orderStatus: (h) => this.swapApi().orderStatus(h,
|
|
12217
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, record2.srcChainId),
|
|
11783
12218
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
11784
12219
|
now: () => Date.now()
|
|
11785
12220
|
};
|
|
11786
12221
|
try {
|
|
11787
12222
|
await runSameChainOrder(runner, {
|
|
11788
|
-
orderHash:
|
|
12223
|
+
orderHash: record2.orderHash,
|
|
12224
|
+
hasPartialFill: record2.status === "partially-filled",
|
|
11789
12225
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11790
12226
|
...options.onStage ? {
|
|
11791
|
-
onStage: (stage) => options.onStage(
|
|
12227
|
+
onStage: (stage) => options.onStage(record2.orderHash, stage)
|
|
11792
12228
|
} : {}
|
|
11793
12229
|
});
|
|
11794
|
-
updateWithdrawSwap(
|
|
12230
|
+
updateWithdrawSwap(record2.orderHash, { status: "landed" });
|
|
11795
12231
|
} catch (error) {
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
});
|
|
12232
|
+
if (error instanceof OwneyError && error.details?.watchActive) return;
|
|
12233
|
+
const outcome = error instanceof OwneyError ? error.details?.outcome : void 0;
|
|
12234
|
+
updateWithdrawSwap(record2.orderHash, { status: outcome === "not-submitted" ? "not-swapped" : outcome === "partially-filled" ? "partially-filled" : "unknown" });
|
|
11800
12235
|
}
|
|
11801
|
-
}
|
|
12236
|
+
});
|
|
11802
12237
|
}
|
|
11803
12238
|
/**
|
|
11804
12239
|
* What the wallet actually holds, across every supported chain, restricted
|
|
@@ -11954,6 +12389,92 @@ var OwneySDK = class {
|
|
|
11954
12389
|
);
|
|
11955
12390
|
}
|
|
11956
12391
|
}
|
|
12392
|
+
/** Read-only confirmation of a saved pre-swap withdrawal. Never withdraws or swaps. */
|
|
12393
|
+
async recheckWithdrawalSwap(id) {
|
|
12394
|
+
const record2 = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
|
|
12395
|
+
const state = this.requireState();
|
|
12396
|
+
if (!record2?.recovery || record2.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || record2.srcChainId !== this.requireChainId() || record2.recovery.phase === "submitted")
|
|
12397
|
+
throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "Reconnect the original wallet and source chain to check this withdrawal.");
|
|
12398
|
+
if (this.withdrawalSwapSubmissions.has(`${state.walletAddress.toLowerCase()}:${record2.srcChainId}:${record2.srcSymbol}`))
|
|
12399
|
+
throw new OwneyError("WITHDRAW_IN_PROGRESS", "This withdrawal is still being observed by its original attempt.");
|
|
12400
|
+
const revision = this.connectionRevision;
|
|
12401
|
+
const asset = SupportedAssets.find((a) => a.chainId === record2.srcChainId && a.symbol === record2.srcSymbol);
|
|
12402
|
+
if (!asset || !record2.recovery.results)
|
|
12403
|
+
throw new OwneyError(
|
|
12404
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12405
|
+
"Some withdrawal results remain unknown. Check Activity and balances before continuing.",
|
|
12406
|
+
{ reason: "WITHDRAWAL_RESULTS_UNAVAILABLE", swapSubmitted: false }
|
|
12407
|
+
);
|
|
12408
|
+
const rpc2 = createPublicClient4({
|
|
12409
|
+
chain: VIEM_CHAIN2[record2.srcChainId],
|
|
12410
|
+
transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls)
|
|
12411
|
+
});
|
|
12412
|
+
const recovery = record2.recovery;
|
|
12413
|
+
const assertRecoveryCurrent = () => {
|
|
12414
|
+
if (this.state !== state || this.connectionRevision !== revision)
|
|
12415
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed during confirmation.");
|
|
12416
|
+
const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
|
|
12417
|
+
if (!latest?.recovery || latest.recovery.phase === "submitted")
|
|
12418
|
+
throw new OwneyError("WITHDRAW_IN_PROGRESS", "This intent has already advanced. Check its current order.");
|
|
12419
|
+
};
|
|
12420
|
+
const persist = () => {
|
|
12421
|
+
assertRecoveryCurrent();
|
|
12422
|
+
saveWithdrawSwap(record2);
|
|
12423
|
+
};
|
|
12424
|
+
for (const [id2, result] of Object.entries(recovery.results)) {
|
|
12425
|
+
const evidence = recovery.evidence[id2] ?? result.evidence;
|
|
12426
|
+
if (evidence) result.evidence = { ...evidence, transfer: "pending", delivery: "pending" };
|
|
12427
|
+
if (id2 === "yieldseeker" && recovery.requested === void 0) result.completion = "unverified";
|
|
12428
|
+
}
|
|
12429
|
+
try {
|
|
12430
|
+
const arrived = await awaitWithdrawalPayouts({
|
|
12431
|
+
results: recovery.results,
|
|
12432
|
+
requested: recovery.requested === void 0 ? void 0 : BigInt(recovery.requested),
|
|
12433
|
+
walletAddress: state.walletAddress,
|
|
12434
|
+
chainId: record2.srcChainId,
|
|
12435
|
+
token: asset.symbol,
|
|
12436
|
+
submittedAt: recovery.submittedAt,
|
|
12437
|
+
baseline: BigInt(recovery.baseline),
|
|
12438
|
+
baselineBlock: BigInt(recovery.baselineBlock),
|
|
12439
|
+
timeoutMs: 3e4,
|
|
12440
|
+
assertCurrent: assertRecoveryCurrent,
|
|
12441
|
+
readBalance: () => rpc2.readContract({ address: asset.address, abi: erc20Abi7, functionName: "balanceOf", args: [state.walletAddress] }),
|
|
12442
|
+
inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
|
|
12443
|
+
resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt: recovery.submittedAt }),
|
|
12444
|
+
history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
|
|
12445
|
+
verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
|
|
12446
|
+
onProgress: (value) => {
|
|
12447
|
+
recovery.evidence[value.agentId] = value;
|
|
12448
|
+
persist();
|
|
12449
|
+
}
|
|
12450
|
+
});
|
|
12451
|
+
record2.withdrawn = arrived.toString();
|
|
12452
|
+
record2.status = "ready";
|
|
12453
|
+
recovery.phase = "ready";
|
|
12454
|
+
recovery.reason = void 0;
|
|
12455
|
+
persist();
|
|
12456
|
+
return record2;
|
|
12457
|
+
} catch (error) {
|
|
12458
|
+
record2.status = "withdrawal-incomplete";
|
|
12459
|
+
recovery.reason = error instanceof OwneyError ? String(error.details?.reason ?? error.code) : "CONFIRMATION_UNAVAILABLE";
|
|
12460
|
+
try {
|
|
12461
|
+
persist();
|
|
12462
|
+
} catch {
|
|
12463
|
+
}
|
|
12464
|
+
throw error;
|
|
12465
|
+
}
|
|
12466
|
+
}
|
|
12467
|
+
/** Explicit foreground continuation: spends verified payouts, never withdraws again. */
|
|
12468
|
+
async continueWithdrawalSwap(id, callbacks = {}) {
|
|
12469
|
+
const record2 = await this.recheckWithdrawalSwap(id);
|
|
12470
|
+
return this.withdrawAndSwap({
|
|
12471
|
+
from: { chainId: record2.srcChainId, symbol: record2.srcSymbol },
|
|
12472
|
+
to: { chainId: record2.dstChainId, symbol: record2.dstSymbol, decimals: record2.dstDecimals },
|
|
12473
|
+
...record2.recovery?.agentId ? { agentId: record2.recovery.agentId } : {},
|
|
12474
|
+
recoveryId: id,
|
|
12475
|
+
...callbacks
|
|
12476
|
+
});
|
|
12477
|
+
}
|
|
11957
12478
|
/**
|
|
11958
12479
|
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
11959
12480
|
* to hold, delivered to their own wallet.
|
|
@@ -11982,7 +12503,14 @@ var OwneySDK = class {
|
|
|
11982
12503
|
const state = this.requireState();
|
|
11983
12504
|
const activeChainId = this.requireChainId();
|
|
11984
12505
|
const revision = this.connectionRevision;
|
|
12506
|
+
let active2 = true;
|
|
12507
|
+
let record2;
|
|
12508
|
+
let hadDurableRecord = false;
|
|
12509
|
+
const startedAt = Date.now();
|
|
12510
|
+
let confirmationDeadline = Infinity;
|
|
11985
12511
|
const assertCurrent = () => {
|
|
12512
|
+
options.assertCurrent?.();
|
|
12513
|
+
if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
|
|
11986
12514
|
if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
|
|
11987
12515
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
|
|
11988
12516
|
};
|
|
@@ -12024,30 +12552,95 @@ var OwneySDK = class {
|
|
|
12024
12552
|
functionName: "balanceOf",
|
|
12025
12553
|
args: [state.walletAddress]
|
|
12026
12554
|
});
|
|
12027
|
-
const
|
|
12028
|
-
|
|
12555
|
+
const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
|
|
12556
|
+
if (options.recoveryId && (!saved?.recovery?.results || saved.recovery.phase !== "ready" || saved.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || saved.srcChainId !== activeChainId || saved.srcSymbol !== asset.symbol || saved.dstChainId !== options.to.chainId || saved.dstSymbol !== options.to.symbol))
|
|
12557
|
+
throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
|
|
12558
|
+
const prepareTimeout = () => new OwneyError(
|
|
12559
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12560
|
+
"Could not read the source wallet. No new withdrawal was submitted.",
|
|
12561
|
+
{ reason: "BASELINE_UNAVAILABLE", swapSubmitted: false }
|
|
12562
|
+
);
|
|
12563
|
+
const baselineBlock = saved ? BigInt(saved.recovery.baselineBlock) : await bounded(() => srcPublic.getBlockNumber({ cacheTime: 0 }), 2e4, prepareTimeout);
|
|
12564
|
+
const baseline = saved ? BigInt(saved.recovery.baseline) : await bounded(readWalletBalance, 2e4, prepareTimeout);
|
|
12029
12565
|
assertCurrent();
|
|
12030
12566
|
debugLog("owney-sdk", "withdrawAndSwap: baseline", {
|
|
12031
12567
|
asset: `${asset.symbol}@${asset.chainId}`,
|
|
12032
12568
|
baseline: baseline.toString()
|
|
12033
12569
|
});
|
|
12034
12570
|
options.onSwapProgress?.("withdrawing");
|
|
12035
|
-
const submittedAt = Date.now();
|
|
12036
|
-
const requested = options.amount === void 0 ? void 0 :
|
|
12571
|
+
const submittedAt = saved?.recovery?.submittedAt ?? Date.now();
|
|
12572
|
+
const requested = saved ? saved.recovery.requested === void 0 ? void 0 : BigInt(saved.recovery.requested) : options.amount === void 0 ? void 0 : parseUnits4(options.amount, asset.decimals);
|
|
12573
|
+
confirmationDeadline = Date.now() + (options.arrivalTimeoutMs ?? 18e4);
|
|
12574
|
+
const attemptId = saved?.recovery?.attemptId ?? `withdraw-${globalThis.crypto.randomUUID()}`;
|
|
12575
|
+
record2 = saved ?? {
|
|
12576
|
+
orderHash: attemptId,
|
|
12577
|
+
walletAddress: state.walletAddress,
|
|
12578
|
+
status: "withdrawal-pending",
|
|
12579
|
+
withdrawn: "0",
|
|
12580
|
+
expected: "0",
|
|
12581
|
+
createdAt: Date.now(),
|
|
12582
|
+
srcSymbol: asset.symbol,
|
|
12583
|
+
srcChainId: activeChainId,
|
|
12584
|
+
srcDecimals: asset.decimals,
|
|
12585
|
+
dstSymbol: options.to.symbol,
|
|
12586
|
+
dstChainId: options.to.chainId,
|
|
12587
|
+
dstDecimals: options.to.decimals ?? 18,
|
|
12588
|
+
recovery: {
|
|
12589
|
+
attemptId,
|
|
12590
|
+
phase: "withdrawing",
|
|
12591
|
+
baseline: baseline.toString(),
|
|
12592
|
+
baselineBlock: baselineBlock.toString(),
|
|
12593
|
+
submittedAt,
|
|
12594
|
+
deadline: confirmationDeadline,
|
|
12595
|
+
requested: requested?.toString(),
|
|
12596
|
+
agentId: options.agentId,
|
|
12597
|
+
evidence: {}
|
|
12598
|
+
}
|
|
12599
|
+
};
|
|
12600
|
+
hadDurableRecord = saveWithdrawSwap(record2);
|
|
12601
|
+
const assertSubmission = () => {
|
|
12602
|
+
assertCurrent();
|
|
12603
|
+
if (Date.now() >= confirmationDeadline) throw new OwneyError(
|
|
12604
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12605
|
+
"Withdrawal confirmation timed out. Check Activity before retrying.",
|
|
12606
|
+
{ reason: "PAYOUT_CONFIRMATION_TIMEOUT", swapSubmitted: false }
|
|
12607
|
+
);
|
|
12608
|
+
};
|
|
12037
12609
|
const evidence = /* @__PURE__ */ new Map();
|
|
12038
12610
|
const onProgress = (next) => {
|
|
12039
12611
|
if (next.chainId !== activeChainId || next.token !== asset.symbol || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
|
|
12040
12612
|
evidence.set(next.agentId, next);
|
|
12613
|
+
if (record2?.recovery) {
|
|
12614
|
+
record2.recovery.evidence[next.agentId] = next;
|
|
12615
|
+
if (active2) saveWithdrawSwap(record2);
|
|
12616
|
+
else {
|
|
12617
|
+
const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
|
|
12618
|
+
if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
|
|
12619
|
+
...latest.recovery,
|
|
12620
|
+
evidence: { ...latest.recovery.evidence, [next.agentId]: next }
|
|
12621
|
+
} });
|
|
12622
|
+
}
|
|
12623
|
+
}
|
|
12041
12624
|
notifyFunding(options.onWithdrawalProgress, next);
|
|
12042
12625
|
};
|
|
12043
12626
|
let withdraw;
|
|
12044
12627
|
try {
|
|
12045
|
-
withdraw = await this.withdraw({
|
|
12628
|
+
withdraw = saved ? { agentResult: saved.recovery.results, totalWithdrawn: null } : await bounded(() => this.withdraw({
|
|
12046
12629
|
asset: asset.symbol,
|
|
12047
12630
|
...requested === void 0 ? {} : { amount: requested.toString() },
|
|
12048
12631
|
...options.agentId ? { agentId: options.agentId } : {},
|
|
12049
|
-
onProgress
|
|
12050
|
-
|
|
12632
|
+
onProgress,
|
|
12633
|
+
assertCurrent: assertSubmission,
|
|
12634
|
+
onBeforeSubmission: (next) => {
|
|
12635
|
+
assertSubmission();
|
|
12636
|
+
onProgress(next);
|
|
12637
|
+
},
|
|
12638
|
+
observationDeadline: confirmationDeadline
|
|
12639
|
+
}), Math.max(0, confirmationDeadline - Date.now()), () => new OwneyError(
|
|
12640
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12641
|
+
"Withdrawal confirmation timed out. Check Activity and balances before trying again.",
|
|
12642
|
+
{ reason: "WITHDRAWAL_DISPATCH_TIMEOUT", swapSubmitted: false, withdrawalEvidence: Object.fromEntries(evidence) }
|
|
12643
|
+
));
|
|
12051
12644
|
} catch (error) {
|
|
12052
12645
|
const details = error instanceof OwneyError ? error.details : void 0;
|
|
12053
12646
|
const moved = [...evidence.values()].some((value) => value.transfer !== "failed" && (value.delivery === "confirmed" || value.transfer === "unknown" || value.txHash || value.userOpHash || value.callsId || value.withdrawalRequest?.attempted));
|
|
@@ -12069,6 +12662,9 @@ var OwneySDK = class {
|
|
|
12069
12662
|
const results = "agentResult" in withdraw ? withdraw.agentResult : {
|
|
12070
12663
|
[options.agentId ?? withdraw.evidence?.agentId ?? "unknown"]: withdraw
|
|
12071
12664
|
};
|
|
12665
|
+
record2.recovery.results = results;
|
|
12666
|
+
record2.recovery.phase = "verifying";
|
|
12667
|
+
saveWithdrawSwap(record2);
|
|
12072
12668
|
const arrived = await awaitWithdrawalPayouts({
|
|
12073
12669
|
results,
|
|
12074
12670
|
requested,
|
|
@@ -12080,7 +12676,8 @@ var OwneySDK = class {
|
|
|
12080
12676
|
baselineBlock,
|
|
12081
12677
|
assertCurrent,
|
|
12082
12678
|
readBalance: readWalletBalance,
|
|
12083
|
-
|
|
12679
|
+
deadline: confirmationDeadline,
|
|
12680
|
+
verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
|
|
12084
12681
|
inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
|
|
12085
12682
|
resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt }),
|
|
12086
12683
|
history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
|
|
@@ -12088,34 +12685,89 @@ var OwneySDK = class {
|
|
|
12088
12685
|
});
|
|
12089
12686
|
assertCurrent();
|
|
12090
12687
|
const withdrawn = arrived.toString();
|
|
12688
|
+
record2.withdrawn = withdrawn;
|
|
12689
|
+
record2.status = "ready";
|
|
12690
|
+
record2.recovery.phase = "ready";
|
|
12691
|
+
hadDurableRecord = saveWithdrawSwap(record2) || hadDurableRecord;
|
|
12091
12692
|
options.onSwapProgress?.("withdrawn");
|
|
12092
12693
|
try {
|
|
12093
|
-
const quote = await this.swapApi().quote({
|
|
12694
|
+
const quote = await bounded(() => this.swapApi().quote({
|
|
12094
12695
|
from: { ...options.from, amount: withdrawn },
|
|
12095
12696
|
to: options.to,
|
|
12096
12697
|
direction: "withdraw",
|
|
12097
12698
|
walletAddress: state.walletAddress
|
|
12098
|
-
});
|
|
12699
|
+
}), 2e4, () => new OwneyError("SWAP_REQUEST_FAILED", "Swap pricing timed out.", { outcome: "not-submitted" }));
|
|
12099
12700
|
assertCurrent();
|
|
12100
|
-
await
|
|
12701
|
+
await bounded(
|
|
12702
|
+
() => this.ensureSwapChain(quote.src.chainId),
|
|
12703
|
+
18e4,
|
|
12704
|
+
() => new OwneyError("SWAP_WALLET_PROMPT_TIMEOUT", "The wallet did not confirm its chain. No swap was submitted.", { outcome: "not-submitted" })
|
|
12705
|
+
);
|
|
12101
12706
|
assertCurrent();
|
|
12102
|
-
const swap = await executeSwap(this.buildSwapDeps(quote
|
|
12707
|
+
const swap = await executeSwap(this.buildSwapDeps(quote, assertCurrent, (hash2) => {
|
|
12708
|
+
record2.txHash = hash2;
|
|
12709
|
+
saveWithdrawSwap(record2);
|
|
12710
|
+
}), {
|
|
12711
|
+
onTransactionRequested: () => {
|
|
12712
|
+
assertCurrent();
|
|
12713
|
+
record2.status = "unknown";
|
|
12714
|
+
record2.recovery.phase = "submitted";
|
|
12715
|
+
if (!saveWithdrawSwap(record2) && hadDurableRecord)
|
|
12716
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
|
|
12717
|
+
},
|
|
12718
|
+
assertCurrent,
|
|
12719
|
+
sourceReserve: baseline,
|
|
12103
12720
|
quote,
|
|
12104
12721
|
walletAddress: state.walletAddress,
|
|
12105
12722
|
direction: "withdraw",
|
|
12106
12723
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
12107
12724
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
|
|
12108
|
-
|
|
12725
|
+
onOrderPrepared: (orderHash, details) => {
|
|
12726
|
+
assertCurrent();
|
|
12727
|
+
const previous = record2.orderHash;
|
|
12728
|
+
record2.orderHash = orderHash;
|
|
12729
|
+
record2.status = "unknown";
|
|
12730
|
+
record2.recovery.phase = "submitted";
|
|
12731
|
+
record2.spent = details.spent;
|
|
12732
|
+
record2.expected = details.expected;
|
|
12733
|
+
if (!saveWithdrawSwap(record2) && hadDurableRecord) {
|
|
12734
|
+
record2.orderHash = previous;
|
|
12735
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
|
|
12736
|
+
}
|
|
12737
|
+
if (previous !== orderHash) {
|
|
12738
|
+
updateWithdrawSwap(previous, { status: "not-swapped" });
|
|
12739
|
+
clearWithdrawSwap(previous);
|
|
12740
|
+
}
|
|
12741
|
+
},
|
|
12742
|
+
onOrderSubmitted: (orderHash, details) => {
|
|
12109
12743
|
releaseSubmission();
|
|
12110
|
-
|
|
12744
|
+
record2.orderHash = orderHash;
|
|
12745
|
+
record2.status = "swapping";
|
|
12746
|
+
record2.recovery.phase = "submitted";
|
|
12747
|
+
record2.createdAt = Date.now();
|
|
12748
|
+
const durable = saveWithdrawSwap(record2);
|
|
12749
|
+
options.onOrderSubmitted?.({
|
|
12750
|
+
orderHash,
|
|
12751
|
+
withdrawn,
|
|
12752
|
+
expected: details?.expected ?? quote.dst.amount,
|
|
12753
|
+
spent: details?.spent,
|
|
12754
|
+
durable: durable && details?.durable !== false
|
|
12755
|
+
});
|
|
12111
12756
|
}
|
|
12112
12757
|
});
|
|
12758
|
+
record2.status = "landed";
|
|
12759
|
+
record2.received = swap.received;
|
|
12760
|
+
record2.spent = swap.spent;
|
|
12761
|
+
saveWithdrawSwap(record2);
|
|
12113
12762
|
return { withdraw, withdrawn, swap };
|
|
12114
12763
|
} catch (error) {
|
|
12115
12764
|
throw new OwneyError(
|
|
12116
12765
|
"WITHDRAW_SWAP_FAILED",
|
|
12117
|
-
`
|
|
12766
|
+
`The withdrawal completed, but the swap to ${options.to.symbol} could not be confirmed. Check the order and both balances before retrying.`,
|
|
12118
12767
|
{
|
|
12768
|
+
outcome: record2.recovery?.phase === "ready" ? "not-submitted" : "submitted-unknown",
|
|
12769
|
+
...error instanceof OwneyError ? error.details : {},
|
|
12770
|
+
...record2.orderHash.startsWith("0x") ? { orderHash: record2.orderHash, settlementUnconfirmed: true } : {},
|
|
12119
12771
|
withdrawn,
|
|
12120
12772
|
asset: asset.symbol,
|
|
12121
12773
|
chainId: asset.chainId,
|
|
@@ -12126,7 +12778,25 @@ var OwneySDK = class {
|
|
|
12126
12778
|
}
|
|
12127
12779
|
);
|
|
12128
12780
|
}
|
|
12781
|
+
} catch (error) {
|
|
12782
|
+
if (record2) {
|
|
12783
|
+
if (error instanceof OwneyError && error.details) Object.assign(error.details, {
|
|
12784
|
+
attemptId: record2.recovery?.attemptId,
|
|
12785
|
+
phase: record2.recovery?.phase,
|
|
12786
|
+
elapsedMs: Date.now() - startedAt,
|
|
12787
|
+
txHash: record2.txHash,
|
|
12788
|
+
withdrawalEvidence: record2.recovery?.evidence
|
|
12789
|
+
});
|
|
12790
|
+
const details = error instanceof OwneyError ? error.details : void 0;
|
|
12791
|
+
const outcome = details?.outcome;
|
|
12792
|
+
if (record2.recovery && outcome === "not-submitted") record2.recovery.phase = "ready";
|
|
12793
|
+
record2.status = outcome === "refunded" ? "refunded" : outcome === "refund-pending" ? "refund-pending" : outcome === "partially-filled" ? "partially-filled" : record2.recovery?.phase === "submitted" || details?.settlementUnconfirmed ? "unknown" : record2.recovery?.phase === "ready" ? "ready" : "withdrawal-incomplete";
|
|
12794
|
+
if (record2.recovery) record2.recovery.reason = typeof details?.reason === "string" ? details.reason : void 0;
|
|
12795
|
+
saveWithdrawSwap(record2);
|
|
12796
|
+
}
|
|
12797
|
+
throw error;
|
|
12129
12798
|
} finally {
|
|
12799
|
+
active2 = false;
|
|
12130
12800
|
releaseSubmission();
|
|
12131
12801
|
}
|
|
12132
12802
|
}
|
|
@@ -12159,7 +12829,15 @@ var OwneySDK = class {
|
|
|
12159
12829
|
const attempted = /* @__PURE__ */ new Set();
|
|
12160
12830
|
this.withdrawalSubmissions.add(submissionKey);
|
|
12161
12831
|
try {
|
|
12162
|
-
return await this.withdrawFromAgents(options, state, chainId, attempted)
|
|
12832
|
+
return options.observationDeadline === void 0 ? await this.withdrawFromAgents(options, state, chainId, attempted) : await bounded(
|
|
12833
|
+
() => this.withdrawFromAgents(options, state, chainId, attempted),
|
|
12834
|
+
Math.max(0, options.observationDeadline - Date.now()),
|
|
12835
|
+
() => new OwneyError(
|
|
12836
|
+
"WITHDRAW_UNCONFIRMED",
|
|
12837
|
+
"Withdrawal dispatch could not be confirmed before the deadline.",
|
|
12838
|
+
{ phase: "observe", reason: "WITHDRAWAL_DISPATCH_TIMEOUT" }
|
|
12839
|
+
)
|
|
12840
|
+
);
|
|
12163
12841
|
} catch (error) {
|
|
12164
12842
|
for (const id of ids) if (!attempted.has(id)) {
|
|
12165
12843
|
notifyFunding(options.onProgress, {
|
|
@@ -12249,7 +12927,7 @@ var OwneySDK = class {
|
|
|
12249
12927
|
progress(evidence);
|
|
12250
12928
|
adapterReported = false;
|
|
12251
12929
|
try {
|
|
12252
|
-
if (options.onBeforeSubmission)
|
|
12930
|
+
if (options.onBeforeSubmission || options.assertCurrent)
|
|
12253
12931
|
return await agent.withdraw(
|
|
12254
12932
|
state,
|
|
12255
12933
|
chainId,
|
|
@@ -12257,7 +12935,8 @@ var OwneySDK = class {
|
|
|
12257
12935
|
requested2,
|
|
12258
12936
|
progress,
|
|
12259
12937
|
options.gasMode ?? "auto",
|
|
12260
|
-
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
12938
|
+
options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
|
|
12939
|
+
options.assertCurrent
|
|
12261
12940
|
);
|
|
12262
12941
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
12263
12942
|
} catch (error) {
|
|
@@ -12381,7 +13060,7 @@ var OwneySDK = class {
|
|
|
12381
13060
|
if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
|
|
12382
13061
|
if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
|
|
12383
13062
|
const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
|
|
12384
|
-
if (!total ||
|
|
13063
|
+
if (!total || parseUnits4(total.amount, assetInfo.decimals) <= entry.balance) return;
|
|
12385
13064
|
}
|
|
12386
13065
|
try {
|
|
12387
13066
|
const otherAvailable = indexedAvailable - entry.balance;
|