@owney/sdk 0.7.27-beta.0 → 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 +1604 -869
- package/dist/index.d.cts +90 -73
- package/dist/index.d.ts +90 -73
- package/dist/index.js +1481 -746
- 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.
|
|
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,23 +5964,23 @@ 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.
|
|
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
|
-
const group = (await checks).slice(index *
|
|
5699
|
-
if (!
|
|
5977
|
+
const group = (await checks).slice(index * 2, index * 2 + 2);
|
|
5978
|
+
if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
|
|
5700
5979
|
this.assertSession(generation, chainId, asset);
|
|
5701
|
-
const [underlying, shares
|
|
5702
|
-
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n
|
|
5703
|
-
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw:
|
|
5704
|
-
if (shares > 0n
|
|
5980
|
+
const [underlying, shares] = group.map((value) => value.result);
|
|
5981
|
+
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
|
|
5982
|
+
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: "0" };
|
|
5983
|
+
if (shares > 0n) {
|
|
5705
5984
|
let positiveValue = false;
|
|
5706
5985
|
const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
|
|
5707
5986
|
try {
|
|
@@ -5712,9 +5991,11 @@ var YieldseekerAgent = class {
|
|
|
5712
5991
|
args: [shares],
|
|
5713
5992
|
blockNumber
|
|
5714
5993
|
});
|
|
5715
|
-
if (typeof value !== "bigint" || value < 0n) return null;
|
|
5994
|
+
if (!active2 || typeof value !== "bigint" || value < 0n) return null;
|
|
5995
|
+
this.assertSession(generation, chainId, asset);
|
|
5716
5996
|
if (value > 0n && !positiveValue) {
|
|
5717
5997
|
positiveValue = true;
|
|
5998
|
+
verified.withdrawableAssetsRaw = value.toString();
|
|
5718
5999
|
verifiedPositions.push(verified);
|
|
5719
6000
|
}
|
|
5720
6001
|
return value;
|
|
@@ -5722,7 +6003,12 @@ var YieldseekerAgent = class {
|
|
|
5722
6003
|
return null;
|
|
5723
6004
|
}
|
|
5724
6005
|
}));
|
|
5725
|
-
if (positiveValue)
|
|
6006
|
+
if (positiveValue) {
|
|
6007
|
+
const redemptionValue = valuations[1];
|
|
6008
|
+
if (redemptionValue !== null && redemptionValue > 0n)
|
|
6009
|
+
verified.withdrawableAssetsRaw = redemptionValue.toString();
|
|
6010
|
+
return verified;
|
|
6011
|
+
}
|
|
5726
6012
|
if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
|
|
5727
6013
|
zeroValuePositions.add(verified);
|
|
5728
6014
|
}
|
|
@@ -5733,7 +6019,7 @@ var YieldseekerAgent = class {
|
|
|
5733
6019
|
}
|
|
5734
6020
|
});
|
|
5735
6021
|
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5736
|
-
|
|
6022
|
+
active2 = false;
|
|
5737
6023
|
this.assertSession(generation, chainId, asset);
|
|
5738
6024
|
return {
|
|
5739
6025
|
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
@@ -5745,13 +6031,14 @@ var YieldseekerAgent = class {
|
|
|
5745
6031
|
const response = await this.walletRequest(
|
|
5746
6032
|
state,
|
|
5747
6033
|
chainId,
|
|
5748
|
-
this.agentPath(context, "
|
|
6034
|
+
this.agentPath(context, "vault-positions")
|
|
5749
6035
|
);
|
|
5750
6036
|
this.assertSession(generation, chainId, asset);
|
|
5751
|
-
if (!Array.isArray(response?.
|
|
5752
|
-
return validatePositions(response.
|
|
6037
|
+
if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
|
|
6038
|
+
return validatePositions(response.vaultPositions);
|
|
5753
6039
|
};
|
|
5754
6040
|
if (amount === void 0) {
|
|
6041
|
+
rememberPositions(priorPositions);
|
|
5755
6042
|
rememberPositions(funds.indexedPositions);
|
|
5756
6043
|
rememberPositions(positions);
|
|
5757
6044
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
@@ -5778,7 +6065,7 @@ var YieldseekerAgent = class {
|
|
|
5778
6065
|
throw new OwneyError(
|
|
5779
6066
|
"WITHDRAW_FAILED",
|
|
5780
6067
|
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5781
|
-
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.
|
|
6068
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.vaultOption.address) },
|
|
5782
6069
|
this.id
|
|
5783
6070
|
);
|
|
5784
6071
|
}
|
|
@@ -5827,6 +6114,7 @@ var YieldseekerAgent = class {
|
|
|
5827
6114
|
let latestUnwindBlock;
|
|
5828
6115
|
const unwind = async (position2, assetsRaw) => {
|
|
5829
6116
|
this.assertSession(generation, chainId, asset);
|
|
6117
|
+
assertOperation?.();
|
|
5830
6118
|
let unwindTransactionHash;
|
|
5831
6119
|
try {
|
|
5832
6120
|
const response = await this.walletRequest(
|
|
@@ -5835,7 +6123,8 @@ var YieldseekerAgent = class {
|
|
|
5835
6123
|
this.agentPath(context, "withdraw-from-position"),
|
|
5836
6124
|
{
|
|
5837
6125
|
method: "POST",
|
|
5838
|
-
body: { chainId, vaultAddress: position2.
|
|
6126
|
+
body: { chainId, vaultAddress: position2.vaultOption.address, assetsRaw: assetsRaw.toString() },
|
|
6127
|
+
assertCurrent: assertOperation
|
|
5839
6128
|
}
|
|
5840
6129
|
);
|
|
5841
6130
|
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
@@ -5854,7 +6143,7 @@ var YieldseekerAgent = class {
|
|
|
5854
6143
|
...error instanceof OwneyError ? error.details : {},
|
|
5855
6144
|
withdrawalPhase: "unwind",
|
|
5856
6145
|
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5857
|
-
vaultAddress: position2.
|
|
6146
|
+
vaultAddress: position2.vaultOption.address,
|
|
5858
6147
|
unwindTransactionHash,
|
|
5859
6148
|
ownerTransferSubmitted: false
|
|
5860
6149
|
},
|
|
@@ -5867,7 +6156,7 @@ var YieldseekerAgent = class {
|
|
|
5867
6156
|
};
|
|
5868
6157
|
if (amount === void 0) {
|
|
5869
6158
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
5870
|
-
const before = new Map(positions.map((position2) => [position2.
|
|
6159
|
+
const before = new Map(positions.map((position2) => [position2.vaultOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
5871
6160
|
let unwound = false;
|
|
5872
6161
|
for (const position2 of positions) {
|
|
5873
6162
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -5884,14 +6173,16 @@ var YieldseekerAgent = class {
|
|
|
5884
6173
|
if (catalog) rememberPositions(catalog);
|
|
5885
6174
|
this.assertSession(generation, chainId, asset);
|
|
5886
6175
|
if (proof.verified) {
|
|
5887
|
-
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.
|
|
5888
|
-
} else if (catalog)
|
|
5889
|
-
|
|
6176
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.vaultOption.address.toLowerCase()))];
|
|
6177
|
+
} else if (catalog) {
|
|
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()))];
|
|
6180
|
+
} else {
|
|
5890
6181
|
positionsStatus = "unavailable";
|
|
5891
6182
|
break;
|
|
5892
6183
|
}
|
|
5893
6184
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5894
|
-
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;
|
|
5895
6186
|
}
|
|
5896
6187
|
} else {
|
|
5897
6188
|
for (const position2 of positions) {
|
|
@@ -5904,7 +6195,7 @@ var YieldseekerAgent = class {
|
|
|
5904
6195
|
}
|
|
5905
6196
|
}
|
|
5906
6197
|
if (amount !== void 0 && remaining > 0n) {
|
|
5907
|
-
throw this.invalidResponse("
|
|
6198
|
+
throw this.invalidResponse("vault positions", {
|
|
5908
6199
|
reason: "Withdrawable positions could not cover the request.",
|
|
5909
6200
|
remaining: remaining.toString()
|
|
5910
6201
|
});
|
|
@@ -5939,6 +6230,13 @@ var YieldseekerAgent = class {
|
|
|
5939
6230
|
const account = getAddress5(state.walletAddress);
|
|
5940
6231
|
let receipt;
|
|
5941
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);
|
|
5942
6240
|
let submissionEvidence = base7;
|
|
5943
6241
|
const data = amount === void 0 ? encodeFunctionData2({
|
|
5944
6242
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
@@ -5956,6 +6254,7 @@ var YieldseekerAgent = class {
|
|
|
5956
6254
|
client: this.getReceiptClient(),
|
|
5957
6255
|
apiKey: this.owneyApiKey,
|
|
5958
6256
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
6257
|
+
assertCurrent: assertOperation,
|
|
5959
6258
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5960
6259
|
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
5961
6260
|
userId: context.user.userId,
|
|
@@ -5968,6 +6267,7 @@ var YieldseekerAgent = class {
|
|
|
5968
6267
|
notifyFunding(onProgress, submissionEvidence);
|
|
5969
6268
|
},
|
|
5970
6269
|
onBeforeSubmission: async (pending) => {
|
|
6270
|
+
assertOperation?.();
|
|
5971
6271
|
await onBeforeSubmission?.({ ...base7, ...pending });
|
|
5972
6272
|
onOwnerTransferAttempted?.();
|
|
5973
6273
|
}
|
|
@@ -5976,15 +6276,26 @@ var YieldseekerAgent = class {
|
|
|
5976
6276
|
receipt = submitted.receipt;
|
|
5977
6277
|
} else {
|
|
5978
6278
|
onOwnerTransferAttempted?.();
|
|
5979
|
-
txHash = await this.submitTransaction(
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
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
|
+
);
|
|
5988
6299
|
}
|
|
5989
6300
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
|
|
5990
6301
|
notifyFunding(onProgress, evidence);
|
|
@@ -6029,7 +6340,7 @@ var YieldseekerAgent = class {
|
|
|
6029
6340
|
asset,
|
|
6030
6341
|
positionsStatus,
|
|
6031
6342
|
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
6032
|
-
vaultAddress: position2.
|
|
6343
|
+
vaultAddress: position2.vaultOption.address,
|
|
6033
6344
|
sharesRaw: position2.assetsRaw,
|
|
6034
6345
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
6035
6346
|
})) : void 0,
|
|
@@ -6038,6 +6349,7 @@ var YieldseekerAgent = class {
|
|
|
6038
6349
|
this.id
|
|
6039
6350
|
);
|
|
6040
6351
|
}
|
|
6352
|
+
if (evidence.maxCompletion) evidence.maxCompletion.vaults = [...knownPositions.keys()];
|
|
6041
6353
|
if (!proof.verified) completion = "unverified";
|
|
6042
6354
|
}
|
|
6043
6355
|
if (explicitEmptyPayout)
|
|
@@ -6070,7 +6382,7 @@ var YieldseekerAgent = class {
|
|
|
6070
6382
|
}
|
|
6071
6383
|
}
|
|
6072
6384
|
}
|
|
6073
|
-
/**
|
|
6385
|
+
/** Fresh position amounts for withdrawal planning, net of reserved fees. */
|
|
6074
6386
|
async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
|
|
6075
6387
|
this.assertChain(chainId);
|
|
6076
6388
|
this.assertAsset(asset);
|
|
@@ -6095,7 +6407,66 @@ var YieldseekerAgent = class {
|
|
|
6095
6407
|
);
|
|
6096
6408
|
return proof.available;
|
|
6097
6409
|
}
|
|
6098
|
-
|
|
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
|
+
}
|
|
6469
|
+
async readVaultShares(options, wallet, blockNumber) {
|
|
6099
6470
|
const owner = getAddress5(wallet);
|
|
6100
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));
|
|
6101
6472
|
const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
|
|
@@ -6105,8 +6476,7 @@ var YieldseekerAgent = class {
|
|
|
6105
6476
|
const address = getAddress5(option.address);
|
|
6106
6477
|
return [
|
|
6107
6478
|
{ address, abi: erc4626Abi2, functionName: "asset" },
|
|
6108
|
-
{ address, abi: erc20Abi5, functionName: "balanceOf", args: [owner] }
|
|
6109
|
-
{ address, abi: erc4626Abi2, functionName: "maxWithdraw", args: [owner] }
|
|
6479
|
+
{ address, abi: erc20Abi5, functionName: "balanceOf", args: [owner] }
|
|
6110
6480
|
];
|
|
6111
6481
|
})
|
|
6112
6482
|
})));
|
|
@@ -6116,23 +6486,23 @@ var YieldseekerAgent = class {
|
|
|
6116
6486
|
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
6117
6487
|
const seen = /* @__PURE__ */ new Set();
|
|
6118
6488
|
for (const value of values) {
|
|
6119
|
-
const option = value?.
|
|
6489
|
+
const option = value?.vaultOption;
|
|
6120
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))
|
|
6121
|
-
throw this.invalidResponse("
|
|
6491
|
+
throw this.invalidResponse("vault positions");
|
|
6122
6492
|
seen.add(option.address.toLowerCase());
|
|
6123
6493
|
}
|
|
6124
6494
|
return values;
|
|
6125
6495
|
}
|
|
6126
6496
|
withdrawalVaultHints(state, chainId, context) {
|
|
6127
6497
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6128
|
-
const positions = this.readCache.get(`positions:${key2}`)?.value?.
|
|
6129
|
-
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]));
|
|
6130
6500
|
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
6131
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()));
|
|
6132
|
-
for (const option of this.
|
|
6502
|
+
for (const option of this.vaultOptions.get(context.asset)?.value ?? []) {
|
|
6133
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;
|
|
6134
6504
|
hints.set(option.address.toLowerCase(), {
|
|
6135
|
-
|
|
6505
|
+
vaultOption: option,
|
|
6136
6506
|
assetsRaw: "0",
|
|
6137
6507
|
assetsBase: "0",
|
|
6138
6508
|
assetsUsd: 0,
|
|
@@ -6166,7 +6536,7 @@ var YieldseekerAgent = class {
|
|
|
6166
6536
|
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
6167
6537
|
const generation = this.readGeneration;
|
|
6168
6538
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
6169
|
-
const version = this.
|
|
6539
|
+
const version = this.liquidityVersions.get(contextKey) ?? 0;
|
|
6170
6540
|
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
6171
6541
|
const unavailable = () => new OwneyError(
|
|
6172
6542
|
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
@@ -6174,12 +6544,12 @@ var YieldseekerAgent = class {
|
|
|
6174
6544
|
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
6175
6545
|
this.id
|
|
6176
6546
|
);
|
|
6177
|
-
let
|
|
6547
|
+
let active2 = true;
|
|
6178
6548
|
let knownProof;
|
|
6179
|
-
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.
|
|
6549
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
6180
6550
|
const assertReadCurrent = () => {
|
|
6181
6551
|
this.assertSession(generation, chainId, context.asset);
|
|
6182
|
-
if (!
|
|
6552
|
+
if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
|
|
6183
6553
|
};
|
|
6184
6554
|
const read2 = async () => {
|
|
6185
6555
|
const client = this.getReceiptClient();
|
|
@@ -6190,11 +6560,10 @@ var YieldseekerAgent = class {
|
|
|
6190
6560
|
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
6191
6561
|
const candidates = /* @__PURE__ */ new Map();
|
|
6192
6562
|
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
6193
|
-
candidates.set(position2.
|
|
6194
|
-
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);
|
|
6195
6565
|
const options = [...candidates.values()];
|
|
6196
6566
|
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
6197
|
-
const checksRead = this.readVaultLiquidity(options, wallet, blockNumber).then((value) => ({ value }), (error) => ({ error }));
|
|
6198
6567
|
const [idle, feeTracker] = await Promise.all([
|
|
6199
6568
|
client.readContract({
|
|
6200
6569
|
address: metadata.address,
|
|
@@ -6221,7 +6590,7 @@ var YieldseekerAgent = class {
|
|
|
6221
6590
|
});
|
|
6222
6591
|
if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
|
|
6223
6592
|
const verified = [];
|
|
6224
|
-
const
|
|
6593
|
+
const resolvedVaults = [];
|
|
6225
6594
|
let deployed = 0n;
|
|
6226
6595
|
const proof = (complete) => ({
|
|
6227
6596
|
blockNumber,
|
|
@@ -6229,53 +6598,102 @@ var YieldseekerAgent = class {
|
|
|
6229
6598
|
fees,
|
|
6230
6599
|
available: idle + deployed > fees ? idle + deployed - fees : 0n,
|
|
6231
6600
|
positions: [...verified],
|
|
6232
|
-
|
|
6601
|
+
resolvedVaults: [...resolvedVaults],
|
|
6233
6602
|
indexedPositions,
|
|
6234
6603
|
complete
|
|
6235
6604
|
});
|
|
6236
6605
|
assertReadCurrent();
|
|
6237
6606
|
knownProof = proof(false);
|
|
6238
6607
|
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6239
|
-
const
|
|
6608
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
|
|
6609
|
+
const indexedValue = indexedPositions.reduce((sum, position2) => sum + BigInt(position2.withdrawableAssetsRaw), 0n);
|
|
6610
|
+
const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.vaultOption.address.toLowerCase()));
|
|
6611
|
+
const unvaluedPosition = indexedPositions.some((position2) => BigInt(position2.assetsRaw) > 0n && BigInt(position2.withdrawableAssetsRaw) === 0n);
|
|
6612
|
+
const indexedAvailable = idle + indexedValue > fees ? idle + indexedValue - fees : 0n;
|
|
6613
|
+
if (indexedValue > 0n && !missingKnownPosition && !unvaluedPosition && (requested === void 0 || indexedAvailable >= requested)) {
|
|
6614
|
+
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
6615
|
+
for (const position2 of indexedPositions) if (BigInt(position2.withdrawableAssetsRaw) > 0n)
|
|
6616
|
+
known.add(position2.vaultOption.address.toLowerCase());
|
|
6617
|
+
this.balanceVaults.set(contextKey, known);
|
|
6618
|
+
return {
|
|
6619
|
+
blockNumber,
|
|
6620
|
+
idle,
|
|
6621
|
+
fees,
|
|
6622
|
+
available: indexedAvailable,
|
|
6623
|
+
positions: indexedPositions,
|
|
6624
|
+
resolvedVaults: [...indexed.keys()],
|
|
6625
|
+
indexedPositions,
|
|
6626
|
+
complete: true
|
|
6627
|
+
};
|
|
6628
|
+
}
|
|
6629
|
+
const collect = async (options2, results) => {
|
|
6240
6630
|
assertReadCurrent();
|
|
6241
6631
|
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
6242
6632
|
this.balanceVaults.set(contextKey, known);
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
const
|
|
6247
|
-
if (
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
return;
|
|
6252
|
-
}
|
|
6253
|
-
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
6254
|
-
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
|
|
6255
|
-
valid2 = false;
|
|
6256
|
-
return;
|
|
6257
|
-
}
|
|
6258
|
-
verifiedVaults.push(option.address.toLowerCase());
|
|
6259
|
-
deployed += withdrawable;
|
|
6260
|
-
verified.push({
|
|
6261
|
-
yieldOption: option,
|
|
6633
|
+
const valid2 = await Promise.all(options2.map(async (option, index) => {
|
|
6634
|
+
const group = results.slice(index * 2, index * 2 + 2);
|
|
6635
|
+
if (group.length !== 2 || group.some((value2) => value2.status !== "success")) return false;
|
|
6636
|
+
const [underlying, shares] = group.map((value2) => value2.result);
|
|
6637
|
+
if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
|
|
6638
|
+
if (shares > 0n) known.add(option.address.toLowerCase());
|
|
6639
|
+
const position2 = {
|
|
6640
|
+
vaultOption: option,
|
|
6262
6641
|
assetsRaw: shares.toString(),
|
|
6263
6642
|
assetsBase: "0",
|
|
6264
6643
|
assetsUsd: 0,
|
|
6265
|
-
withdrawableAssetsRaw:
|
|
6266
|
-
withdrawableAssetsBase:
|
|
6267
|
-
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6644
|
+
withdrawableAssetsRaw: "0",
|
|
6645
|
+
withdrawableAssetsBase: "0"
|
|
6646
|
+
};
|
|
6647
|
+
resolvedVaults.push(option.address.toLowerCase());
|
|
6648
|
+
verified.push(position2);
|
|
6649
|
+
knownProof = proof(false);
|
|
6650
|
+
if (shares === 0n) return true;
|
|
6651
|
+
let preview;
|
|
6652
|
+
try {
|
|
6653
|
+
const value2 = await client.readContract({
|
|
6654
|
+
address: getAddress5(option.address),
|
|
6655
|
+
abi: erc4626Abi2,
|
|
6656
|
+
functionName: "previewRedeem",
|
|
6657
|
+
args: [shares],
|
|
6658
|
+
blockNumber
|
|
6659
|
+
});
|
|
6660
|
+
if (typeof value2 === "bigint" && value2 >= 0n) preview = value2;
|
|
6661
|
+
} catch {
|
|
6662
|
+
}
|
|
6663
|
+
assertReadCurrent();
|
|
6664
|
+
let value = preview;
|
|
6665
|
+
if (value === void 0 || value === 0n) {
|
|
6666
|
+
try {
|
|
6667
|
+
const converted = await client.readContract({
|
|
6668
|
+
address: getAddress5(option.address),
|
|
6669
|
+
abi: erc4626Abi2,
|
|
6670
|
+
functionName: "convertToAssets",
|
|
6671
|
+
args: [shares],
|
|
6672
|
+
blockNumber
|
|
6673
|
+
});
|
|
6674
|
+
if (typeof converted === "bigint" && converted >= 0n && (converted > 0n || preview === 0n)) value = converted;
|
|
6675
|
+
else value = void 0;
|
|
6676
|
+
} catch {
|
|
6677
|
+
value = void 0;
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
assertReadCurrent();
|
|
6681
|
+
if (value === void 0) return false;
|
|
6682
|
+
position2.withdrawableAssetsRaw = value.toString();
|
|
6683
|
+
position2.withdrawableAssetsBase = value.toString();
|
|
6684
|
+
deployed += value;
|
|
6685
|
+
knownProof = proof(false);
|
|
6686
|
+
return true;
|
|
6687
|
+
}));
|
|
6688
|
+
return valid2.every(Boolean);
|
|
6270
6689
|
};
|
|
6271
|
-
const checks = await
|
|
6690
|
+
const checks = await this.readVaultShares(options, wallet, blockNumber);
|
|
6272
6691
|
assertReadCurrent();
|
|
6273
|
-
|
|
6274
|
-
const allKnownVerified = collect(options, checks.value);
|
|
6692
|
+
const allKnownVerified = await collect(options, checks);
|
|
6275
6693
|
knownProof = proof(false);
|
|
6276
|
-
|
|
6694
|
+
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6277
6695
|
const discrepancy = verified.some((position2) => {
|
|
6278
|
-
const row = indexed.get(position2.
|
|
6696
|
+
const row = indexed.get(position2.vaultOption.address.toLowerCase());
|
|
6279
6697
|
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
6280
6698
|
});
|
|
6281
6699
|
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
@@ -6284,7 +6702,7 @@ var YieldseekerAgent = class {
|
|
|
6284
6702
|
assertReadCurrent();
|
|
6285
6703
|
return proof(true);
|
|
6286
6704
|
}
|
|
6287
|
-
const catalog = await this.
|
|
6705
|
+
const catalog = await this.loadVaultOptions(context.asset).catch(() => void 0);
|
|
6288
6706
|
assertReadCurrent();
|
|
6289
6707
|
const eligible = /* @__PURE__ */ new Map();
|
|
6290
6708
|
for (const option of catalog ?? []) {
|
|
@@ -6293,7 +6711,7 @@ var YieldseekerAgent = class {
|
|
|
6293
6711
|
}
|
|
6294
6712
|
const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
|
|
6295
6713
|
if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
6296
|
-
const allExtraVerified = collect(extra, await this.
|
|
6714
|
+
const allExtraVerified = await collect(extra, await this.readVaultShares(extra, wallet, blockNumber));
|
|
6297
6715
|
assertReadCurrent();
|
|
6298
6716
|
const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
|
|
6299
6717
|
if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
|
|
@@ -6310,7 +6728,7 @@ var YieldseekerAgent = class {
|
|
|
6310
6728
|
if (error instanceof OwneyError) throw error;
|
|
6311
6729
|
throw unavailable();
|
|
6312
6730
|
} finally {
|
|
6313
|
-
|
|
6731
|
+
active2 = false;
|
|
6314
6732
|
if (timer) clearTimeout(timer);
|
|
6315
6733
|
}
|
|
6316
6734
|
}
|
|
@@ -6324,7 +6742,7 @@ var YieldseekerAgent = class {
|
|
|
6324
6742
|
this.balanceContexts.set(read2, { context, generation: this.readGeneration });
|
|
6325
6743
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6326
6744
|
const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
|
|
6327
|
-
for (const position2 of context.positions) known.add(position2.
|
|
6745
|
+
for (const position2 of context.positions) known.add(position2.vaultOption.address.toLowerCase());
|
|
6328
6746
|
this.balanceVaults.set(key2, known);
|
|
6329
6747
|
}
|
|
6330
6748
|
return balance;
|
|
@@ -6363,7 +6781,7 @@ var YieldseekerAgent = class {
|
|
|
6363
6781
|
const total = idle + invested > fees ? idle + invested - fees : 0n;
|
|
6364
6782
|
const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
6365
6783
|
if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
|
|
6366
|
-
const amount =
|
|
6784
|
+
const amount = parseUnits2(displayed.amount, asset.decimals);
|
|
6367
6785
|
if (amount < 0n) return void 0;
|
|
6368
6786
|
const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
|
|
6369
6787
|
const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
|
|
@@ -6462,13 +6880,13 @@ var YieldseekerAgent = class {
|
|
|
6462
6880
|
});
|
|
6463
6881
|
const catalog = await Promise.all(
|
|
6464
6882
|
[...new Set(contexts.map((context) => context.asset))].map(
|
|
6465
|
-
(contextAsset) => this.
|
|
6883
|
+
(contextAsset) => this.loadVaultOptions(contextAsset)
|
|
6466
6884
|
)
|
|
6467
6885
|
);
|
|
6468
6886
|
const vaultAddresses = new Set(
|
|
6469
6887
|
catalog.flat().filter(
|
|
6470
|
-
(
|
|
6471
|
-
).map((
|
|
6888
|
+
(vaultOption) => vaultOption.chainId === chainId && isAddress4(vaultOption.address)
|
|
6889
|
+
).map((vaultOption) => vaultOption.address.toLowerCase())
|
|
6472
6890
|
);
|
|
6473
6891
|
return mapYieldseekerHistory(contexts, {
|
|
6474
6892
|
limit: options?.limit ?? 10,
|
|
@@ -6493,30 +6911,30 @@ var YieldseekerAgent = class {
|
|
|
6493
6911
|
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
6494
6912
|
const values = await Promise.all(
|
|
6495
6913
|
assets.map(async (asset) => {
|
|
6496
|
-
return { asset,
|
|
6914
|
+
return { asset, vaultOptions: await this.loadVaultOptions(asset) };
|
|
6497
6915
|
})
|
|
6498
6916
|
);
|
|
6499
6917
|
return mapYieldseekerAgentApy(values, days);
|
|
6500
6918
|
}
|
|
6501
|
-
async
|
|
6502
|
-
const cached = this.
|
|
6919
|
+
async loadVaultOptions(asset) {
|
|
6920
|
+
const cached = this.vaultOptions.get(asset);
|
|
6503
6921
|
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
6504
|
-
const pending = this.
|
|
6922
|
+
const pending = this.pendingVaultOptions.get(asset);
|
|
6505
6923
|
if (pending) return pending;
|
|
6506
6924
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
6507
6925
|
const request2 = this.api.request(
|
|
6508
|
-
`/chains/8453/assets/${metadata.address}/
|
|
6926
|
+
`/chains/8453/assets/${metadata.address}/vault-options`
|
|
6509
6927
|
).then((response) => {
|
|
6510
|
-
if (!Array.isArray(response?.
|
|
6511
|
-
throw this.invalidResponse("
|
|
6928
|
+
if (!Array.isArray(response?.vaultOptions)) {
|
|
6929
|
+
throw this.invalidResponse("vault options");
|
|
6512
6930
|
}
|
|
6513
|
-
this.
|
|
6514
|
-
expiresAt: Date.now() +
|
|
6515
|
-
value: response.
|
|
6931
|
+
this.vaultOptions.set(asset, {
|
|
6932
|
+
expiresAt: Date.now() + YIELDSEEKER_VAULT_OPTIONS_CACHE_MS,
|
|
6933
|
+
value: response.vaultOptions
|
|
6516
6934
|
});
|
|
6517
|
-
return response.
|
|
6518
|
-
}).finally(() => this.
|
|
6519
|
-
this.
|
|
6935
|
+
return response.vaultOptions;
|
|
6936
|
+
}).finally(() => this.pendingVaultOptions.delete(asset));
|
|
6937
|
+
this.pendingVaultOptions.set(asset, request2);
|
|
6520
6938
|
return request2;
|
|
6521
6939
|
}
|
|
6522
6940
|
userKey(state, chainId) {
|
|
@@ -6826,9 +7244,10 @@ var YieldseekerAgent = class {
|
|
|
6826
7244
|
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
6827
7245
|
};
|
|
6828
7246
|
}
|
|
6829
|
-
advancePortfolioVersion(contextKey) {
|
|
7247
|
+
advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
|
|
6830
7248
|
const version = this.portfolioVersion(contextKey) + 1;
|
|
6831
7249
|
this.portfolioVersions.set(contextKey, version);
|
|
7250
|
+
if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
|
|
6832
7251
|
this.snapshotFailures.delete(contextKey);
|
|
6833
7252
|
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
6834
7253
|
const key2 = `${kind}:${contextKey}`;
|
|
@@ -6876,7 +7295,7 @@ var YieldseekerAgent = class {
|
|
|
6876
7295
|
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
6877
7296
|
const failure = this.snapshotFailures.get(contextKey);
|
|
6878
7297
|
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
6879
|
-
this.advancePortfolioVersion(contextKey);
|
|
7298
|
+
this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
|
|
6880
7299
|
}
|
|
6881
7300
|
const version = this.portfolioVersion(contextKey);
|
|
6882
7301
|
const generation = this.readGeneration;
|
|
@@ -6959,17 +7378,17 @@ var YieldseekerAgent = class {
|
|
|
6959
7378
|
}
|
|
6960
7379
|
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
6961
7380
|
const [positions, historic, actions] = await Promise.all([
|
|
6962
|
-
options.includePositions === false ? Promise.resolve({
|
|
7381
|
+
options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
|
|
6963
7382
|
`positions:${contextKey}`,
|
|
6964
7383
|
this.portfolioCacheMs(contextKey),
|
|
6965
7384
|
async () => {
|
|
6966
7385
|
const response = await this.walletRequest(
|
|
6967
7386
|
state,
|
|
6968
7387
|
chainId,
|
|
6969
|
-
this.agentPath(context, "
|
|
7388
|
+
this.agentPath(context, "vault-positions")
|
|
6970
7389
|
);
|
|
6971
|
-
if (!Array.isArray(response?.
|
|
6972
|
-
throw this.invalidResponse("
|
|
7390
|
+
if (!Array.isArray(response?.vaultPositions)) {
|
|
7391
|
+
throw this.invalidResponse("vault positions");
|
|
6973
7392
|
}
|
|
6974
7393
|
return response;
|
|
6975
7394
|
}
|
|
@@ -6994,7 +7413,7 @@ var YieldseekerAgent = class {
|
|
|
6994
7413
|
) : Promise.resolve(void 0)
|
|
6995
7414
|
]);
|
|
6996
7415
|
return {
|
|
6997
|
-
positions: positions.
|
|
7416
|
+
positions: positions.vaultPositions,
|
|
6998
7417
|
...historic?.position ? { historic: historic.position } : {},
|
|
6999
7418
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
7000
7419
|
};
|
|
@@ -7065,10 +7484,10 @@ var YieldseekerAgent = class {
|
|
|
7065
7484
|
}
|
|
7066
7485
|
async providerRequest(state, chainId, path, options = {}) {
|
|
7067
7486
|
this.assertChain(chainId);
|
|
7068
|
-
const request2 = (signature2) =>
|
|
7069
|
-
|
|
7070
|
-
signature: signature2
|
|
7071
|
-
}
|
|
7487
|
+
const request2 = (signature2) => {
|
|
7488
|
+
options.assertCurrent?.();
|
|
7489
|
+
return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
|
|
7490
|
+
};
|
|
7072
7491
|
let signature = await this.auth.getToken(state, chainId);
|
|
7073
7492
|
try {
|
|
7074
7493
|
return await request2(signature);
|
|
@@ -7370,45 +7789,57 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
|
7370
7789
|
// src/lib/swap/swap-api.ts
|
|
7371
7790
|
async function request(baseUrl, apiKey, path, init) {
|
|
7372
7791
|
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
7373
|
-
const
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
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(
|
|
7406
7836
|
"SWAP_REQUEST_FAILED",
|
|
7407
|
-
|
|
7408
|
-
{
|
|
7409
|
-
);
|
|
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();
|
|
7410
7842
|
}
|
|
7411
|
-
return json.data;
|
|
7412
7843
|
}
|
|
7413
7844
|
function createSwapApi(baseUrl, apiKey) {
|
|
7414
7845
|
return {
|
|
@@ -7714,8 +8145,12 @@ async function awaitBalanceIncrease(options) {
|
|
|
7714
8145
|
let lastError;
|
|
7715
8146
|
for (; ; ) {
|
|
7716
8147
|
try {
|
|
7717
|
-
const balance = await
|
|
7718
|
-
|
|
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) {
|
|
7719
8154
|
const arrived = balance - baseline;
|
|
7720
8155
|
debugLog("owney-sdk", "arrival: funds landed", {
|
|
7721
8156
|
arrived: arrived.toString()
|
|
@@ -7741,7 +8176,7 @@ async function awaitBalanceIncrease(options) {
|
|
|
7741
8176
|
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
7742
8177
|
} : { arrived: null };
|
|
7743
8178
|
}
|
|
7744
|
-
await sleep(pollMs);
|
|
8179
|
+
await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
7745
8180
|
}
|
|
7746
8181
|
}
|
|
7747
8182
|
|
|
@@ -7794,7 +8229,7 @@ var DEFAULT_POLL_MS2 = 5e3;
|
|
|
7794
8229
|
var MAX_BACKOFF_MS = 3e4;
|
|
7795
8230
|
var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
7796
8231
|
var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
|
|
7797
|
-
async function
|
|
8232
|
+
async function runFusionOrderInternal(deps, options) {
|
|
7798
8233
|
const {
|
|
7799
8234
|
orderHash,
|
|
7800
8235
|
secrets,
|
|
@@ -7803,39 +8238,51 @@ async function runFusionOrder(deps, options) {
|
|
|
7803
8238
|
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
7804
8239
|
} = options;
|
|
7805
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
|
+
};
|
|
7806
8261
|
let failures = 0;
|
|
7807
8262
|
const published = /* @__PURE__ */ new Set();
|
|
7808
8263
|
onStage?.("swapping");
|
|
7809
8264
|
for (; ; ) {
|
|
7810
|
-
|
|
7811
|
-
throw new OwneyError(
|
|
7812
|
-
"SWAP_REQUEST_FAILED",
|
|
7813
|
-
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
7814
|
-
// Marks the one failure that means "we stopped watching a live order",
|
|
7815
|
-
// as opposed to a verdict or a prompt nobody answered. The executor
|
|
7816
|
-
// checks the wallet before believing it: resolvers do not stop filling
|
|
7817
|
-
// because we stopped looking (ROUT-242, comment 15980).
|
|
7818
|
-
{ orderHash, settlementUnconfirmed: true }
|
|
7819
|
-
);
|
|
7820
|
-
}
|
|
8265
|
+
assertTime();
|
|
7821
8266
|
let ready;
|
|
7822
8267
|
try {
|
|
7823
|
-
ready = await deps.readyForSecrets(orderHash);
|
|
8268
|
+
ready = await read2(() => deps.readyForSecrets(orderHash));
|
|
7824
8269
|
} catch {
|
|
7825
8270
|
ready = {};
|
|
7826
8271
|
}
|
|
8272
|
+
assertTime();
|
|
7827
8273
|
for (const fill of ready.fills ?? []) {
|
|
8274
|
+
assertTime();
|
|
7828
8275
|
if (published.has(fill.idx)) continue;
|
|
7829
8276
|
const secret = secrets[fill.idx];
|
|
7830
8277
|
if (secret === void 0) {
|
|
7831
8278
|
throw new OwneyError(
|
|
7832
8279
|
"SWAP_REQUEST_FAILED",
|
|
7833
8280
|
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
7834
|
-
{ orderHash, fillIndex: fill.idx }
|
|
8281
|
+
{ orderHash, fillIndex: fill.idx, settlementUnconfirmed: true, outcome: "submitted-unknown" }
|
|
7835
8282
|
);
|
|
7836
8283
|
}
|
|
7837
8284
|
try {
|
|
7838
|
-
await deps.submitSecret(orderHash, secret);
|
|
8285
|
+
await read2(() => deps.submitSecret(orderHash, secret));
|
|
7839
8286
|
published.add(fill.idx);
|
|
7840
8287
|
} catch {
|
|
7841
8288
|
failures += 1;
|
|
@@ -7843,29 +8290,34 @@ async function runFusionOrder(deps, options) {
|
|
|
7843
8290
|
}
|
|
7844
8291
|
let status;
|
|
7845
8292
|
try {
|
|
7846
|
-
|
|
8293
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8294
|
+
status = state.status;
|
|
8295
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7847
8296
|
failures = 0;
|
|
7848
8297
|
} catch {
|
|
7849
8298
|
failures += 1;
|
|
7850
|
-
await
|
|
8299
|
+
await pause(backoffFor(failures, pollIntervalMs));
|
|
7851
8300
|
continue;
|
|
7852
8301
|
}
|
|
7853
8302
|
if (status === "refunding") onStage?.("refunding");
|
|
7854
8303
|
if (isSwapTerminal(status)) {
|
|
7855
|
-
if (status === "executed") {
|
|
8304
|
+
if (status === "executed" || status === "filled") {
|
|
7856
8305
|
onStage?.("swapped");
|
|
7857
8306
|
return { status, filled: true };
|
|
7858
8307
|
}
|
|
7859
|
-
if (status === "refunded") onStage?.("refunded");
|
|
8308
|
+
if (status === "refunded") onStage?.(partial ? "swap-partial" : "refunded");
|
|
7860
8309
|
throw new OwneyError(
|
|
7861
8310
|
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7862
|
-
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.",
|
|
7863
|
-
{ 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" }
|
|
7864
8313
|
);
|
|
7865
8314
|
}
|
|
7866
|
-
await
|
|
8315
|
+
await pause(pollIntervalMs);
|
|
7867
8316
|
}
|
|
7868
8317
|
}
|
|
8318
|
+
function runFusionOrder(deps, options) {
|
|
8319
|
+
return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
|
|
8320
|
+
}
|
|
7869
8321
|
|
|
7870
8322
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
7871
8323
|
var DEFAULT_POLL_MS3 = 5e3;
|
|
@@ -7890,7 +8342,7 @@ function explain(status) {
|
|
|
7890
8342
|
return "The swap did not complete. Nothing was exchanged.";
|
|
7891
8343
|
}
|
|
7892
8344
|
}
|
|
7893
|
-
async function
|
|
8345
|
+
async function runSameChainOrderInternal(deps, options) {
|
|
7894
8346
|
const {
|
|
7895
8347
|
orderHash,
|
|
7896
8348
|
onStage,
|
|
@@ -7898,23 +8350,39 @@ async function runSameChainOrder(deps, options) {
|
|
|
7898
8350
|
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
7899
8351
|
} = options;
|
|
7900
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
|
+
};
|
|
7901
8373
|
let failures = 0;
|
|
7902
8374
|
onStage?.("swapping");
|
|
7903
8375
|
for (; ; ) {
|
|
7904
|
-
|
|
7905
|
-
throw new OwneyError(
|
|
7906
|
-
"SWAP_ORDER_EXPIRED",
|
|
7907
|
-
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
7908
|
-
{ orderHash }
|
|
7909
|
-
);
|
|
7910
|
-
}
|
|
8376
|
+
assertTime();
|
|
7911
8377
|
let status;
|
|
7912
8378
|
try {
|
|
7913
|
-
|
|
8379
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8380
|
+
status = state.status;
|
|
8381
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7914
8382
|
failures = 0;
|
|
7915
8383
|
} catch {
|
|
7916
8384
|
failures += 1;
|
|
7917
|
-
await
|
|
8385
|
+
await pause(backoffFor2(failures, pollIntervalMs));
|
|
7918
8386
|
continue;
|
|
7919
8387
|
}
|
|
7920
8388
|
if (isSwapTerminal(status)) {
|
|
@@ -7924,17 +8392,19 @@ async function runSameChainOrder(deps, options) {
|
|
|
7924
8392
|
}
|
|
7925
8393
|
throw new OwneyError(
|
|
7926
8394
|
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7927
|
-
explain(status),
|
|
7928
|
-
{ 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" }
|
|
7929
8397
|
);
|
|
7930
8398
|
}
|
|
7931
|
-
await
|
|
8399
|
+
await pause(pollIntervalMs);
|
|
7932
8400
|
}
|
|
7933
8401
|
}
|
|
8402
|
+
function runSameChainOrder(deps, options) {
|
|
8403
|
+
return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
|
|
8404
|
+
}
|
|
7934
8405
|
|
|
7935
8406
|
// src/lib/swap/swap.secret-store.ts
|
|
7936
8407
|
var KEY_PREFIX4 = "owney.swap.order";
|
|
7937
|
-
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7938
8408
|
var storage4 = () => {
|
|
7939
8409
|
if (typeof window === "undefined") return null;
|
|
7940
8410
|
try {
|
|
@@ -7946,10 +8416,13 @@ var storage4 = () => {
|
|
|
7946
8416
|
var keyFor2 = (orderHash) => `${KEY_PREFIX4}.${orderHash}`;
|
|
7947
8417
|
function saveOrder(order) {
|
|
7948
8418
|
const store = storage4();
|
|
7949
|
-
if (!store) return;
|
|
8419
|
+
if (!store) return false;
|
|
7950
8420
|
try {
|
|
7951
|
-
|
|
8421
|
+
const value = JSON.stringify(order);
|
|
8422
|
+
store.setItem(keyFor2(order.orderHash), value);
|
|
8423
|
+
return store.getItem(keyFor2(order.orderHash)) === value;
|
|
7952
8424
|
} catch {
|
|
8425
|
+
return false;
|
|
7953
8426
|
}
|
|
7954
8427
|
}
|
|
7955
8428
|
function clearOrder(orderHash) {
|
|
@@ -7960,7 +8433,7 @@ function clearOrder(orderHash) {
|
|
|
7960
8433
|
} catch {
|
|
7961
8434
|
}
|
|
7962
8435
|
}
|
|
7963
|
-
function listOrders(
|
|
8436
|
+
function listOrders(_now = Date.now()) {
|
|
7964
8437
|
const store = storage4();
|
|
7965
8438
|
if (!store) return [];
|
|
7966
8439
|
const out = [];
|
|
@@ -7975,10 +8448,6 @@ function listOrders(now = Date.now()) {
|
|
|
7975
8448
|
if (!raw2) continue;
|
|
7976
8449
|
try {
|
|
7977
8450
|
const parsed = JSON.parse(raw2);
|
|
7978
|
-
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
7979
|
-
store.removeItem(key2);
|
|
7980
|
-
continue;
|
|
7981
|
-
}
|
|
7982
8451
|
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
7983
8452
|
out.push(parsed);
|
|
7984
8453
|
}
|
|
@@ -8019,12 +8488,11 @@ async function awaitWalletPrompt(prompt, what) {
|
|
|
8019
8488
|
if (timer) clearTimeout(timer);
|
|
8020
8489
|
}
|
|
8021
8490
|
}
|
|
8022
|
-
function stoppedWatching(error) {
|
|
8023
|
-
return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
|
|
8024
|
-
}
|
|
8025
8491
|
var DEFAULT_SLIPPAGE = 1;
|
|
8026
|
-
async function affordableAmount(deps, quoted) {
|
|
8027
|
-
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" });
|
|
8028
8496
|
if (balance >= quoted) return quoted;
|
|
8029
8497
|
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
8030
8498
|
quoted: quoted.toString(),
|
|
@@ -8035,48 +8503,72 @@ async function affordableAmount(deps, quoted) {
|
|
|
8035
8503
|
}
|
|
8036
8504
|
async function executeSwap(deps, options) {
|
|
8037
8505
|
const { quote, walletAddress, onStage } = options;
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
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
|
+
};
|
|
8049
8544
|
try {
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
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
|
|
8056
8551
|
});
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
if (railError) throw railError;
|
|
8066
|
-
throw new OwneyError(
|
|
8067
|
-
"SWAP_REQUEST_FAILED",
|
|
8068
|
-
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
8069
|
-
{ rail: quote.rail, ...result ?? {} }
|
|
8070
|
-
);
|
|
8071
|
-
}
|
|
8072
|
-
if (railError) {
|
|
8073
|
-
debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
|
|
8074
|
-
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 }
|
|
8075
8560
|
});
|
|
8076
|
-
|
|
8077
|
-
|
|
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;
|
|
8078
8571
|
}
|
|
8079
|
-
return { received: arrived.toString(), ...result ?? {} };
|
|
8080
8572
|
}
|
|
8081
8573
|
async function runClassic(deps, options) {
|
|
8082
8574
|
const {
|
|
@@ -8087,7 +8579,7 @@ async function runClassic(deps, options) {
|
|
|
8087
8579
|
onStage
|
|
8088
8580
|
} = options;
|
|
8089
8581
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8090
|
-
|
|
8582
|
+
let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8091
8583
|
const swapTxRequest = {
|
|
8092
8584
|
from: {
|
|
8093
8585
|
chainId: quote.src.chainId,
|
|
@@ -8119,25 +8611,36 @@ async function runClassic(deps, options) {
|
|
|
8119
8611
|
"owney-sdk",
|
|
8120
8612
|
"swap: re-fetching classic calldata after approval"
|
|
8121
8613
|
);
|
|
8614
|
+
amount = await affordableAmount(deps, amount, options.sourceReserve);
|
|
8615
|
+
swapTxRequest.from.amount = amount.toString();
|
|
8122
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" });
|
|
8123
8619
|
}
|
|
8124
8620
|
}
|
|
8125
8621
|
onStage?.("signing");
|
|
8126
8622
|
await deps.ensureChain(quote.src.chainId);
|
|
8127
8623
|
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
8624
|
+
options.assertCurrent?.();
|
|
8625
|
+
options.onTransactionRequested?.();
|
|
8128
8626
|
const txHash = await deps.sendTransaction({
|
|
8129
8627
|
to: tx.to,
|
|
8130
8628
|
data: tx.data,
|
|
8131
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;
|
|
8132
8634
|
});
|
|
8133
8635
|
onStage?.("swapped");
|
|
8134
|
-
return { txHash };
|
|
8636
|
+
return { txHash, spent: amount.toString() };
|
|
8135
8637
|
}
|
|
8136
8638
|
async function runSameChainFusion(deps, options, walletAddress) {
|
|
8137
8639
|
const { quote, direction, onStage } = options;
|
|
8138
8640
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8139
|
-
|
|
8140
|
-
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);
|
|
8141
8644
|
onStage?.("building");
|
|
8142
8645
|
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
8143
8646
|
const built = await deps.api.buildOrder({
|
|
@@ -8161,36 +8664,42 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
8161
8664
|
"sign the swap"
|
|
8162
8665
|
);
|
|
8163
8666
|
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
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
|
+
}
|
|
8174
8683
|
try {
|
|
8175
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8684
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable: true });
|
|
8176
8685
|
} catch {
|
|
8177
8686
|
}
|
|
8178
8687
|
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
8179
8688
|
orderHash: built.orderHash,
|
|
8180
8689
|
...onStage ? { onStage } : {}
|
|
8181
8690
|
});
|
|
8182
|
-
return { orderHash: built.orderHash };
|
|
8691
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
8183
8692
|
}
|
|
8184
8693
|
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
8185
8694
|
const { quote, onStage } = options;
|
|
8186
|
-
if (!quote.spender || isNativeSource) return
|
|
8695
|
+
if (!quote.spender || isNativeSource) return { paid: false };
|
|
8187
8696
|
const current = await deps.readAllowance(quote.spender);
|
|
8188
8697
|
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
8189
8698
|
spender: quote.spender,
|
|
8190
8699
|
current: current.toString(),
|
|
8191
8700
|
needed: amount.toString()
|
|
8192
8701
|
});
|
|
8193
|
-
if (current >= amount) return
|
|
8702
|
+
if (current >= amount) return { paid: false };
|
|
8194
8703
|
onStage?.("approving");
|
|
8195
8704
|
await deps.ensureChain(quote.src.chainId);
|
|
8196
8705
|
const permit = quote.permitSupported ? await awaitWalletPrompt(
|
|
@@ -8201,11 +8710,18 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
8201
8710
|
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
8202
8711
|
bytes: (permit.length - 2) / 2
|
|
8203
8712
|
});
|
|
8204
|
-
return permit;
|
|
8713
|
+
return { permit, paid: false };
|
|
8205
8714
|
}
|
|
8206
8715
|
await deps.approve(quote.spender, MAX_UINT256);
|
|
8207
8716
|
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
8208
|
-
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;
|
|
8209
8725
|
}
|
|
8210
8726
|
async function runFusion(deps, options, walletAddress) {
|
|
8211
8727
|
const { quote, direction, onStage } = options;
|
|
@@ -8217,8 +8733,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8217
8733
|
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
8218
8734
|
);
|
|
8219
8735
|
}
|
|
8220
|
-
|
|
8221
|
-
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);
|
|
8222
8739
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
8223
8740
|
onStage?.("building");
|
|
8224
8741
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -8237,8 +8754,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8237
8754
|
...direction ? { direction } : {},
|
|
8238
8755
|
...permit ? { permit } : {}
|
|
8239
8756
|
});
|
|
8240
|
-
saveOrder({
|
|
8757
|
+
const durable = saveOrder({
|
|
8241
8758
|
orderHash: built.orderHash,
|
|
8759
|
+
walletAddress,
|
|
8242
8760
|
secrets,
|
|
8243
8761
|
srcChainId: quote.src.chainId,
|
|
8244
8762
|
srcSymbol: quote.src.symbol,
|
|
@@ -8248,33 +8766,39 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8248
8766
|
createdAt: Date.now()
|
|
8249
8767
|
});
|
|
8250
8768
|
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
(
|
|
8258
|
-
|
|
8259
|
-
|
|
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
|
+
}
|
|
8260
8780
|
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
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
|
+
}
|
|
8275
8799
|
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
8276
8800
|
try {
|
|
8277
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8801
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
|
|
8278
8802
|
} catch {
|
|
8279
8803
|
}
|
|
8280
8804
|
try {
|
|
@@ -8284,13 +8808,25 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8284
8808
|
...onStage ? { onStage } : {}
|
|
8285
8809
|
});
|
|
8286
8810
|
} catch (error) {
|
|
8287
|
-
if (error instanceof OwneyError &&
|
|
8811
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
8288
8812
|
clearOrder(built.orderHash);
|
|
8289
8813
|
}
|
|
8290
8814
|
throw error;
|
|
8291
8815
|
}
|
|
8292
8816
|
clearOrder(built.orderHash);
|
|
8293
|
-
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
|
+
);
|
|
8294
8830
|
}
|
|
8295
8831
|
|
|
8296
8832
|
// src/lib/swap/swap.withdrawal-settlement.ts
|
|
@@ -8300,8 +8836,8 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
8300
8836
|
async function awaitWithdrawalPayouts(options) {
|
|
8301
8837
|
const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
|
|
8302
8838
|
const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
|
|
8303
|
-
const deadline = Date.now() + timeoutMs;
|
|
8304
|
-
let
|
|
8839
|
+
const deadline = options.deadline ?? Date.now() + timeoutMs;
|
|
8840
|
+
let active2 = true;
|
|
8305
8841
|
let timer;
|
|
8306
8842
|
const incomplete = (reason, agentId) => new OwneyError(
|
|
8307
8843
|
"WITHDRAW_SWAP_INCOMPLETE",
|
|
@@ -8319,7 +8855,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8319
8855
|
);
|
|
8320
8856
|
const assertActive = () => {
|
|
8321
8857
|
options.assertCurrent();
|
|
8322
|
-
if (!
|
|
8858
|
+
if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
|
|
8323
8859
|
};
|
|
8324
8860
|
const pause = async () => {
|
|
8325
8861
|
assertActive();
|
|
@@ -8330,10 +8866,11 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8330
8866
|
if (evidence.agentId !== id || evidence.chainId !== options.chainId || evidence.token !== options.token || evidence.walletAddress.toLowerCase() !== options.walletAddress.toLowerCase())
|
|
8331
8867
|
throw incomplete("PAYOUT_SCOPE_MISMATCH", id);
|
|
8332
8868
|
};
|
|
8333
|
-
const
|
|
8869
|
+
const record2 = (id, payout, evidence) => {
|
|
8334
8870
|
assertActive();
|
|
8335
8871
|
validate(id, evidence);
|
|
8336
8872
|
payout.evidence = evidence;
|
|
8873
|
+
payout.result.evidence = evidence;
|
|
8337
8874
|
if (evidence.transfer === "confirmed" && evidence.delivery === "confirmed") payout.amount = rawAmount(evidence.confirmedAmount);
|
|
8338
8875
|
options.onProgress(evidence);
|
|
8339
8876
|
};
|
|
@@ -8398,8 +8935,30 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8398
8935
|
if (payout.amount === void 0 || payout.amount <= 0n) throw incomplete("INVALID_PAYOUT_AMOUNT", id);
|
|
8399
8936
|
if (["yieldseeker", "zyfai"].includes(id) && (!hash(evidence.txHash) || rawAmount(evidence.blockNumber) === void 0 || !evidence.logIndexes?.length))
|
|
8400
8937
|
throw incomplete("PAYOUT_RECEIPT_UNVERIFIED", id);
|
|
8401
|
-
if (options.requested === void 0 && payout.result.completion === "unverified")
|
|
8402
|
-
|
|
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
|
+
}
|
|
8403
8962
|
const intended = rawAmount(evidence.intendedAmount);
|
|
8404
8963
|
if (intended !== void 0 && payout.amount < intended && verifiedFee(payout) !== intended - payout.amount)
|
|
8405
8964
|
throw incomplete("PAYOUT_SHORTFALL", id);
|
|
@@ -8409,24 +8968,24 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8409
8968
|
let next = evidence;
|
|
8410
8969
|
if (!next.txHash && (next.userOpHash || next.callsId)) {
|
|
8411
8970
|
next = await options.resolve(next);
|
|
8412
|
-
|
|
8971
|
+
record2(id, payout, next);
|
|
8413
8972
|
}
|
|
8414
8973
|
if (next.transfer === "failed" || next.delivery === "failed") continue;
|
|
8415
8974
|
if (next.txHash) {
|
|
8416
8975
|
next = await options.inspect(next, false);
|
|
8417
|
-
|
|
8976
|
+
record2(id, payout, next);
|
|
8418
8977
|
if (id === "zyfai" && next.transfer === "unknown" && rawAmount(next.blockNumber) !== void 0 && next.logIndexes?.length === 0 && next.transferLogs?.length === 0 && !next.userOpHash && !next.callsId) {
|
|
8419
8978
|
const found = await findPayout(id, next);
|
|
8420
8979
|
if (found) {
|
|
8421
8980
|
next = found;
|
|
8422
|
-
|
|
8981
|
+
record2(id, payout, next);
|
|
8423
8982
|
}
|
|
8424
8983
|
}
|
|
8425
8984
|
} else if (!next.userOpHash && !next.callsId) {
|
|
8426
8985
|
const found = await findPayout(id, next);
|
|
8427
8986
|
if (found) {
|
|
8428
8987
|
next = found;
|
|
8429
|
-
|
|
8988
|
+
record2(id, payout, next);
|
|
8430
8989
|
}
|
|
8431
8990
|
}
|
|
8432
8991
|
if (next.transfer === "confirmed" && next.delivery === "confirmed" || next.transfer === "failed" || next.delivery === "failed") continue;
|
|
@@ -8470,10 +9029,10 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8470
9029
|
};
|
|
8471
9030
|
try {
|
|
8472
9031
|
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
8473
|
-
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")),
|
|
9032
|
+
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
|
|
8474
9033
|
})]);
|
|
8475
9034
|
} finally {
|
|
8476
|
-
|
|
9035
|
+
active2 = false;
|
|
8477
9036
|
if (timer) clearTimeout(timer);
|
|
8478
9037
|
}
|
|
8479
9038
|
}
|
|
@@ -8494,10 +9053,10 @@ function verifiedFee(payout) {
|
|
|
8494
9053
|
import {
|
|
8495
9054
|
domainSeparator,
|
|
8496
9055
|
encodeAbiParameters,
|
|
8497
|
-
parseAbi as
|
|
9056
|
+
parseAbi as parseAbi5,
|
|
8498
9057
|
parseSignature
|
|
8499
9058
|
} from "viem";
|
|
8500
|
-
var PERMIT_ABI =
|
|
9059
|
+
var PERMIT_ABI = parseAbi5([
|
|
8501
9060
|
"function nonces(address owner) view returns (uint256)",
|
|
8502
9061
|
"function name() view returns (string)",
|
|
8503
9062
|
"function version() view returns (string)",
|
|
@@ -8595,7 +9154,7 @@ async function buildErc2612Permit(reads, input) {
|
|
|
8595
9154
|
|
|
8596
9155
|
// src/lib/swap/swap.withdraw-swap.ts
|
|
8597
9156
|
var KEY_PREFIX5 = "owney.swap.withdraw";
|
|
8598
|
-
var
|
|
9157
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
8599
9158
|
var storage5 = () => {
|
|
8600
9159
|
if (typeof window === "undefined") return null;
|
|
8601
9160
|
try {
|
|
@@ -8620,15 +9179,19 @@ var notify = () => {
|
|
|
8620
9179
|
}
|
|
8621
9180
|
}
|
|
8622
9181
|
};
|
|
8623
|
-
function saveWithdrawSwap(
|
|
9182
|
+
function saveWithdrawSwap(record2) {
|
|
8624
9183
|
const store = storage5();
|
|
8625
|
-
if (!store) return;
|
|
9184
|
+
if (!store) return false;
|
|
8626
9185
|
try {
|
|
8627
|
-
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;
|
|
8628
9190
|
} catch {
|
|
8629
|
-
return;
|
|
9191
|
+
return false;
|
|
8630
9192
|
}
|
|
8631
9193
|
notify();
|
|
9194
|
+
return true;
|
|
8632
9195
|
}
|
|
8633
9196
|
function updateWithdrawSwap(orderHash, patch) {
|
|
8634
9197
|
const store = storage5();
|
|
@@ -8651,13 +9214,17 @@ function clearWithdrawSwap(orderHash) {
|
|
|
8651
9214
|
const store = storage5();
|
|
8652
9215
|
if (!store) return;
|
|
8653
9216
|
try {
|
|
8654
|
-
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));
|
|
8655
9222
|
} catch {
|
|
8656
9223
|
return;
|
|
8657
9224
|
}
|
|
8658
9225
|
notify();
|
|
8659
9226
|
}
|
|
8660
|
-
function listWithdrawSwaps(now = Date.now()) {
|
|
9227
|
+
function listWithdrawSwaps(now = Date.now(), includeDismissed = false) {
|
|
8661
9228
|
const store = storage5();
|
|
8662
9229
|
if (!store) return [];
|
|
8663
9230
|
const out = [];
|
|
@@ -8672,11 +9239,11 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8672
9239
|
if (!raw2) continue;
|
|
8673
9240
|
try {
|
|
8674
9241
|
const parsed = JSON.parse(raw2);
|
|
8675
|
-
if (now - parsed.createdAt >
|
|
9242
|
+
if (["landed", "refunded", "not-swapped"].includes(parsed.status) && now - (parsed.settledAt ?? parsed.createdAt) > MAX_AGE_MS) {
|
|
8676
9243
|
store.removeItem(key2);
|
|
8677
9244
|
continue;
|
|
8678
9245
|
}
|
|
8679
|
-
out.push(parsed);
|
|
9246
|
+
if (includeDismissed || !parsed.dismissed) out.push(parsed);
|
|
8680
9247
|
} catch {
|
|
8681
9248
|
store.removeItem(key2);
|
|
8682
9249
|
}
|
|
@@ -8686,10 +9253,15 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8686
9253
|
}
|
|
8687
9254
|
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
8688
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
|
+
}
|
|
8689
9261
|
|
|
8690
9262
|
// src/lib/swap/swap.pending-deposit.ts
|
|
8691
9263
|
var KEY_PREFIX6 = "owney.swap.pending-deposit";
|
|
8692
|
-
var
|
|
9264
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
8693
9265
|
var storage6 = () => {
|
|
8694
9266
|
if (typeof window === "undefined") return null;
|
|
8695
9267
|
try {
|
|
@@ -8732,7 +9304,7 @@ function listPendingDeposits(now = Date.now()) {
|
|
|
8732
9304
|
if (!raw2) continue;
|
|
8733
9305
|
try {
|
|
8734
9306
|
const parsed = JSON.parse(raw2);
|
|
8735
|
-
if (now - parsed.createdAt >
|
|
9307
|
+
if (now - parsed.createdAt > MAX_AGE_MS2) {
|
|
8736
9308
|
store.removeItem(key2);
|
|
8737
9309
|
continue;
|
|
8738
9310
|
}
|
|
@@ -8781,7 +9353,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
8781
9353
|
}
|
|
8782
9354
|
|
|
8783
9355
|
// src/lib/helpers/withdraw-helper.ts
|
|
8784
|
-
import { parseUnits as
|
|
9356
|
+
import { parseUnits as parseUnits3 } from "viem";
|
|
8785
9357
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
8786
9358
|
return agents.map((agent) => {
|
|
8787
9359
|
let invalidBalance = false;
|
|
@@ -8791,7 +9363,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
8791
9363
|
return 0n;
|
|
8792
9364
|
}
|
|
8793
9365
|
const [whole, fraction] = value.split(".");
|
|
8794
|
-
return raw2 ? BigInt(value) :
|
|
9366
|
+
return raw2 ? BigInt(value) : parseUnits3(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
|
|
8795
9367
|
};
|
|
8796
9368
|
const agentBalance = aggregated[agent.id];
|
|
8797
9369
|
const tokenBalance = agentBalance?.tokens.find(
|
|
@@ -8991,11 +9563,11 @@ function aggregateDailyEarnings(series, tokenSymbol) {
|
|
|
8991
9563
|
const byAsset = /* @__PURE__ */ new Map();
|
|
8992
9564
|
for (const account of series) {
|
|
8993
9565
|
for (const { asset, points } of account.assets) {
|
|
8994
|
-
const
|
|
8995
|
-
if (!points.length || wanted &&
|
|
8996
|
-
const components = byAsset.get(
|
|
9566
|
+
const symbol2 = asset.toUpperCase();
|
|
9567
|
+
if (!points.length || wanted && symbol2 !== wanted) continue;
|
|
9568
|
+
const components = byAsset.get(symbol2) ?? [];
|
|
8997
9569
|
components.push([...points].sort((a, b) => a.date.localeCompare(b.date)));
|
|
8998
|
-
byAsset.set(
|
|
9570
|
+
byAsset.set(symbol2, components);
|
|
8999
9571
|
}
|
|
9000
9572
|
}
|
|
9001
9573
|
return [...byAsset.entries()].map(([asset, components]) => {
|
|
@@ -9024,7 +9596,7 @@ function aggregateDailyEarnings(series, tokenSymbol) {
|
|
|
9024
9596
|
|
|
9025
9597
|
// src/client.ts
|
|
9026
9598
|
import {
|
|
9027
|
-
parseUnits as
|
|
9599
|
+
parseUnits as parseUnits4,
|
|
9028
9600
|
createPublicClient as createPublicClient4,
|
|
9029
9601
|
createWalletClient as createWalletClient5,
|
|
9030
9602
|
custom as custom5,
|
|
@@ -9040,111 +9612,6 @@ import {
|
|
|
9040
9612
|
parseTransaction,
|
|
9041
9613
|
toBytes
|
|
9042
9614
|
} from "viem";
|
|
9043
|
-
|
|
9044
|
-
// src/lib/permit2-batch.ts
|
|
9045
|
-
import { parseAbi as parseAbi5, hashStruct } from "viem";
|
|
9046
|
-
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
9047
|
-
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
9048
|
-
var ERC2612_PERMIT_TYPES = {
|
|
9049
|
-
Permit: [
|
|
9050
|
-
{ name: "owner", type: "address" },
|
|
9051
|
-
{ name: "spender", type: "address" },
|
|
9052
|
-
{ name: "value", type: "uint256" },
|
|
9053
|
-
{ name: "nonce", type: "uint256" },
|
|
9054
|
-
{ name: "deadline", type: "uint256" }
|
|
9055
|
-
]
|
|
9056
|
-
};
|
|
9057
|
-
var ERC2612_READ_ABI = parseAbi5([
|
|
9058
|
-
"function name() view returns (string)",
|
|
9059
|
-
"function version() view returns (string)",
|
|
9060
|
-
"function nonces(address owner) view returns (uint256)"
|
|
9061
|
-
]);
|
|
9062
|
-
function erc2612TypedData(input) {
|
|
9063
|
-
return {
|
|
9064
|
-
domain: {
|
|
9065
|
-
name: input.permit.tokenName,
|
|
9066
|
-
version: input.permit.tokenVersion,
|
|
9067
|
-
chainId: input.chainId,
|
|
9068
|
-
verifyingContract: input.token
|
|
9069
|
-
},
|
|
9070
|
-
types: ERC2612_PERMIT_TYPES,
|
|
9071
|
-
primaryType: "Permit",
|
|
9072
|
-
message: {
|
|
9073
|
-
owner: input.owner,
|
|
9074
|
-
spender: BATCH_PERMIT2_ADDRESS,
|
|
9075
|
-
value: BigInt(input.permit.value),
|
|
9076
|
-
nonce: BigInt(input.permit.nonce),
|
|
9077
|
-
deadline: BigInt(input.permit.deadline)
|
|
9078
|
-
}
|
|
9079
|
-
};
|
|
9080
|
-
}
|
|
9081
|
-
var PERMIT_BATCH_TYPES = {
|
|
9082
|
-
PermitBatchWitnessTransferFrom: [
|
|
9083
|
-
{ name: "permitted", type: "TokenPermissions[]" },
|
|
9084
|
-
{ name: "spender", type: "address" },
|
|
9085
|
-
{ name: "nonce", type: "uint256" },
|
|
9086
|
-
{ name: "deadline", type: "uint256" },
|
|
9087
|
-
{ name: "witness", type: "Deposit" }
|
|
9088
|
-
],
|
|
9089
|
-
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
9090
|
-
TokenPermissions: [
|
|
9091
|
-
{ name: "token", type: "address" },
|
|
9092
|
-
{ name: "amount", type: "uint256" }
|
|
9093
|
-
]
|
|
9094
|
-
};
|
|
9095
|
-
var PERMIT2_BATCH_ABI = parseAbi5([
|
|
9096
|
-
"struct TokenPermissions { address token; uint256 amount; }",
|
|
9097
|
-
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
9098
|
-
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
9099
|
-
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
9100
|
-
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
9101
|
-
]);
|
|
9102
|
-
function batchPermit(b) {
|
|
9103
|
-
return {
|
|
9104
|
-
permitted: b.transfers.map((t) => ({
|
|
9105
|
-
token: b.token,
|
|
9106
|
-
amount: BigInt(t.amount)
|
|
9107
|
-
})),
|
|
9108
|
-
nonce: BigInt(b.nonce),
|
|
9109
|
-
deadline: BigInt(b.deadline)
|
|
9110
|
-
};
|
|
9111
|
-
}
|
|
9112
|
-
function batchTypedData(b, spender) {
|
|
9113
|
-
return {
|
|
9114
|
-
domain: {
|
|
9115
|
-
name: "Permit2",
|
|
9116
|
-
chainId: b.chainId,
|
|
9117
|
-
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
9118
|
-
},
|
|
9119
|
-
types: PERMIT_BATCH_TYPES,
|
|
9120
|
-
primaryType: "PermitBatchWitnessTransferFrom",
|
|
9121
|
-
message: {
|
|
9122
|
-
...batchPermit(b),
|
|
9123
|
-
spender,
|
|
9124
|
-
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
9125
|
-
}
|
|
9126
|
-
};
|
|
9127
|
-
}
|
|
9128
|
-
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
9129
|
-
function batchWitness(b) {
|
|
9130
|
-
return hashStruct({
|
|
9131
|
-
types: PERMIT_BATCH_TYPES,
|
|
9132
|
-
primaryType: "Deposit",
|
|
9133
|
-
data: { recipients: b.transfers.map((t) => t.to) }
|
|
9134
|
-
});
|
|
9135
|
-
}
|
|
9136
|
-
function batchArgs(b) {
|
|
9137
|
-
return [
|
|
9138
|
-
batchPermit(b),
|
|
9139
|
-
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
9140
|
-
b.from,
|
|
9141
|
-
batchWitness(b),
|
|
9142
|
-
BATCH_WITNESS_TYPE,
|
|
9143
|
-
b.signature
|
|
9144
|
-
];
|
|
9145
|
-
}
|
|
9146
|
-
|
|
9147
|
-
// src/lib/sponsored-token-batch.ts
|
|
9148
9615
|
var memory = /* @__PURE__ */ new Map();
|
|
9149
9616
|
var settledRecords = /* @__PURE__ */ new Map();
|
|
9150
9617
|
var expiredRecords = /* @__PURE__ */ new Map();
|
|
@@ -9172,14 +9639,14 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9172
9639
|
let timer;
|
|
9173
9640
|
let expired = false;
|
|
9174
9641
|
const check = async () => {
|
|
9175
|
-
let
|
|
9176
|
-
if (
|
|
9177
|
-
const current = JSON.parse(
|
|
9642
|
+
let active2 = read(key2);
|
|
9643
|
+
if (active2 && i.expectedTxHash) {
|
|
9644
|
+
const current = JSON.parse(active2);
|
|
9178
9645
|
if (!current.serializedTransaction || keccak2562(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
|
|
9179
|
-
|
|
9646
|
+
active2 = void 0;
|
|
9180
9647
|
}
|
|
9181
9648
|
const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
|
|
9182
|
-
const saved =
|
|
9649
|
+
const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
|
|
9183
9650
|
if (!saved) return;
|
|
9184
9651
|
const body = JSON.parse(saved);
|
|
9185
9652
|
if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
|
|
@@ -9203,8 +9670,8 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9203
9670
|
args: [i.owner, nonce >> 8n],
|
|
9204
9671
|
blockNumber: block.number
|
|
9205
9672
|
});
|
|
9206
|
-
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n ||
|
|
9207
|
-
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) {
|
|
9208
9675
|
const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
|
|
9209
9676
|
expiredRecords.set(terminalKey, saved);
|
|
9210
9677
|
let persisted = typeof window === "undefined";
|
|
@@ -9262,15 +9729,15 @@ function clear(key2, body) {
|
|
|
9262
9729
|
function sponsorTokenBatch(i) {
|
|
9263
9730
|
const key2 = pendingKey(i);
|
|
9264
9731
|
const plan = planOf(i.transfers);
|
|
9265
|
-
const
|
|
9266
|
-
if (
|
|
9267
|
-
if (
|
|
9732
|
+
const active2 = inflight.get(key2);
|
|
9733
|
+
if (active2) {
|
|
9734
|
+
if (active2.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
9268
9735
|
return Promise.reject(
|
|
9269
9736
|
new Error(
|
|
9270
9737
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
9271
9738
|
)
|
|
9272
9739
|
);
|
|
9273
|
-
return
|
|
9740
|
+
return active2.promise;
|
|
9274
9741
|
}
|
|
9275
9742
|
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
9276
9743
|
inflight.set(key2, { plan, promise });
|
|
@@ -9424,7 +9891,7 @@ async function execute(i, key2, plan) {
|
|
|
9424
9891
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
9425
9892
|
"Insufficient token balance for this deposit."
|
|
9426
9893
|
);
|
|
9427
|
-
if (allowance < total && (i.chainId
|
|
9894
|
+
if (allowance < total && !supportsSponsoredErc2612(i.chainId, i.token))
|
|
9428
9895
|
throw new OwneyError(
|
|
9429
9896
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
9430
9897
|
"token deposits need a one-time Permit2 approval."
|
|
@@ -9460,7 +9927,7 @@ async function execute(i, key2, plan) {
|
|
|
9460
9927
|
const signature2 = await i.wallet.signTypedData({
|
|
9461
9928
|
account: i.owner,
|
|
9462
9929
|
...erc2612TypedData({
|
|
9463
|
-
chainId:
|
|
9930
|
+
chainId: i.chainId,
|
|
9464
9931
|
token: i.token,
|
|
9465
9932
|
owner: i.owner,
|
|
9466
9933
|
permit: unsignedPermit
|
|
@@ -10088,12 +10555,12 @@ var OwneySDK = class {
|
|
|
10088
10555
|
return [...this.getActiveAgents().keys()].sort();
|
|
10089
10556
|
}
|
|
10090
10557
|
getActiveAgents() {
|
|
10091
|
-
const
|
|
10558
|
+
const active2 = /* @__PURE__ */ new Map();
|
|
10092
10559
|
for (const id of this.activeAgents) {
|
|
10093
10560
|
const agent = this.agents.get(id);
|
|
10094
|
-
if (agent)
|
|
10561
|
+
if (agent) active2.set(id, agent);
|
|
10095
10562
|
}
|
|
10096
|
-
return
|
|
10563
|
+
return active2;
|
|
10097
10564
|
}
|
|
10098
10565
|
/**
|
|
10099
10566
|
* Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
|
|
@@ -10676,10 +11143,10 @@ var OwneySDK = class {
|
|
|
10676
11143
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
10677
11144
|
* two independent auto-recovery mechanisms:
|
|
10678
11145
|
*
|
|
10679
|
-
* 1. Missing Permit2 allowance outside the
|
|
11146
|
+
* 1. Missing Permit2 allowance outside the sponsored ERC-2612 path: when the
|
|
10680
11147
|
* app did not supply its own callback and the attempt fails with
|
|
10681
11148
|
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
10682
|
-
* for that token. Base USDC
|
|
11149
|
+
* for that token. Native Base/Arbitrum USDC uses gasless ERC-2612 approval in
|
|
10683
11150
|
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
10684
11151
|
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
10685
11152
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
@@ -11186,8 +11653,8 @@ var OwneySDK = class {
|
|
|
11186
11653
|
async ensureSwapChain(chainId) {
|
|
11187
11654
|
const provider = this.requireConnectedProvider();
|
|
11188
11655
|
const state = this.requireState();
|
|
11189
|
-
const
|
|
11190
|
-
if (!
|
|
11656
|
+
const chain2 = swapChain(chainId);
|
|
11657
|
+
if (!chain2) {
|
|
11191
11658
|
throw new OwneyError(
|
|
11192
11659
|
"CHAIN_UNSUPPORTED",
|
|
11193
11660
|
`Chain ${chainId} is not supported`,
|
|
@@ -11195,10 +11662,10 @@ var OwneySDK = class {
|
|
|
11195
11662
|
);
|
|
11196
11663
|
}
|
|
11197
11664
|
await ensureWalletOnChain(
|
|
11198
|
-
createPublicClient4({ chain, transport: custom5(provider) }),
|
|
11665
|
+
createPublicClient4({ chain: chain2, transport: custom5(provider) }),
|
|
11199
11666
|
createWalletClient5({
|
|
11200
11667
|
account: state.walletAddress,
|
|
11201
|
-
chain,
|
|
11668
|
+
chain: chain2,
|
|
11202
11669
|
transport: custom5(provider)
|
|
11203
11670
|
}),
|
|
11204
11671
|
chainId
|
|
@@ -11222,15 +11689,15 @@ var OwneySDK = class {
|
|
|
11222
11689
|
async wrapNativeSource(quote, walletAddress, onStage, direction) {
|
|
11223
11690
|
const wrapper = requireWrapper(quote);
|
|
11224
11691
|
const chainId = quote.src.chainId;
|
|
11225
|
-
const
|
|
11692
|
+
const chain2 = swapChain(chainId);
|
|
11226
11693
|
const provider = this.requireConnectedProvider();
|
|
11227
11694
|
const publicClient = createPublicClient4({
|
|
11228
|
-
chain,
|
|
11695
|
+
chain: chain2,
|
|
11229
11696
|
transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
|
|
11230
11697
|
});
|
|
11231
11698
|
const wallet = createWalletClient5({
|
|
11232
11699
|
account: walletAddress,
|
|
11233
|
-
chain,
|
|
11700
|
+
chain: chain2,
|
|
11234
11701
|
transport: custom5(provider)
|
|
11235
11702
|
});
|
|
11236
11703
|
onStage?.("wrapping");
|
|
@@ -11257,7 +11724,7 @@ var OwneySDK = class {
|
|
|
11257
11724
|
data: tx.data,
|
|
11258
11725
|
value: BigInt(tx.value),
|
|
11259
11726
|
account: walletAddress,
|
|
11260
|
-
chain
|
|
11727
|
+
chain: chain2
|
|
11261
11728
|
});
|
|
11262
11729
|
onSubmitted?.(hash2);
|
|
11263
11730
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
@@ -11327,15 +11794,27 @@ var OwneySDK = class {
|
|
|
11327
11794
|
* Kept as a builder rather than baked into the executor so the whole swap
|
|
11328
11795
|
* flow stays testable without a provider — the executor never imports viem.
|
|
11329
11796
|
*/
|
|
11330
|
-
buildSwapDeps(quote) {
|
|
11797
|
+
buildSwapDeps(quote, assertOperation, onTransactionHash) {
|
|
11331
11798
|
const state = this.requireState();
|
|
11332
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
|
+
};
|
|
11333
11806
|
const srcChain = swapChain(quote.src.chainId);
|
|
11334
11807
|
const dstChain = swapChain(quote.dst.chainId);
|
|
11335
11808
|
const wallet = createWalletClient5({
|
|
11336
11809
|
account: state.walletAddress,
|
|
11337
11810
|
chain: srcChain,
|
|
11338
|
-
|
|
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 })
|
|
11339
11818
|
});
|
|
11340
11819
|
const srcPublic = createPublicClient4({
|
|
11341
11820
|
chain: srcChain,
|
|
@@ -11364,6 +11843,7 @@ var OwneySDK = class {
|
|
|
11364
11843
|
});
|
|
11365
11844
|
},
|
|
11366
11845
|
sendTransaction: async (tx) => {
|
|
11846
|
+
assertWallet();
|
|
11367
11847
|
const hash2 = await wallet.sendTransaction({
|
|
11368
11848
|
to: tx.to,
|
|
11369
11849
|
data: tx.data,
|
|
@@ -11371,24 +11851,34 @@ var OwneySDK = class {
|
|
|
11371
11851
|
account: state.walletAddress,
|
|
11372
11852
|
chain: srcChain
|
|
11373
11853
|
});
|
|
11854
|
+
onTransactionHash?.(hash2);
|
|
11374
11855
|
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11375
11856
|
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
11376
11857
|
hash: hash2,
|
|
11377
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
|
+
);
|
|
11378
11865
|
});
|
|
11379
11866
|
if (receipt.status !== "success") {
|
|
11380
11867
|
throw new OwneyError(
|
|
11381
11868
|
"SWAP_REQUEST_FAILED",
|
|
11382
11869
|
`Swap transaction reverted (tx ${hash2})`,
|
|
11383
|
-
{
|
|
11870
|
+
{ txHash: hash2, outcome: "not-submitted", executionFailed: true }
|
|
11384
11871
|
);
|
|
11385
11872
|
}
|
|
11386
11873
|
return hash2;
|
|
11387
11874
|
},
|
|
11388
|
-
signTypedData: (typedData) =>
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11875
|
+
signTypedData: (typedData) => {
|
|
11876
|
+
assertWallet();
|
|
11877
|
+
return wallet.signTypedData({
|
|
11878
|
+
account: state.walletAddress,
|
|
11879
|
+
...typedData
|
|
11880
|
+
});
|
|
11881
|
+
},
|
|
11392
11882
|
// Chain-bound like every other read here: the wallet provider's chain is
|
|
11393
11883
|
// not ours to rely on mid-swap.
|
|
11394
11884
|
readSourceBalance: async () => {
|
|
@@ -11410,6 +11900,7 @@ var OwneySDK = class {
|
|
|
11410
11900
|
args: [state.walletAddress, spender]
|
|
11411
11901
|
}),
|
|
11412
11902
|
approve: async (spender, amount) => {
|
|
11903
|
+
assertWallet();
|
|
11413
11904
|
const hash2 = await wallet.writeContract({
|
|
11414
11905
|
address: quote.src.address,
|
|
11415
11906
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -11418,14 +11909,22 @@ var OwneySDK = class {
|
|
|
11418
11909
|
account: state.walletAddress,
|
|
11419
11910
|
chain: srcChain
|
|
11420
11911
|
});
|
|
11421
|
-
await srcPublic.waitForTransactionReceipt({
|
|
11912
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11422
11913
|
hash: hash2,
|
|
11423
11914
|
confirmations: 1,
|
|
11424
11915
|
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
11425
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
|
+
);
|
|
11426
11922
|
return hash2;
|
|
11427
11923
|
},
|
|
11428
|
-
ensureChain: (chainId) =>
|
|
11924
|
+
ensureChain: (chainId) => {
|
|
11925
|
+
assertWallet();
|
|
11926
|
+
return this.ensureSwapChain(chainId);
|
|
11927
|
+
},
|
|
11429
11928
|
/**
|
|
11430
11929
|
* Sign the approval instead of paying for it. Null means the token
|
|
11431
11930
|
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
@@ -11445,10 +11944,13 @@ var OwneySDK = class {
|
|
|
11445
11944
|
spender,
|
|
11446
11945
|
value: amount,
|
|
11447
11946
|
chainId: quote.src.chainId,
|
|
11448
|
-
signTypedData: (payload) =>
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11947
|
+
signTypedData: (payload) => {
|
|
11948
|
+
assertWallet();
|
|
11949
|
+
return wallet.signTypedData({
|
|
11950
|
+
account: state.walletAddress,
|
|
11951
|
+
...payload
|
|
11952
|
+
});
|
|
11953
|
+
}
|
|
11452
11954
|
}
|
|
11453
11955
|
),
|
|
11454
11956
|
/**
|
|
@@ -11498,36 +12000,20 @@ var OwneySDK = class {
|
|
|
11498
12000
|
);
|
|
11499
12001
|
}
|
|
11500
12002
|
/**
|
|
11501
|
-
*
|
|
11502
|
-
*
|
|
11503
|
-
*
|
|
11504
|
-
*
|
|
11505
|
-
*
|
|
11506
|
-
*
|
|
11507
|
-
* Something does. A first WETH deposit needs a one-time Permit2 approval,
|
|
11508
|
-
* and that approval is a transaction. A wallet holding no native gas token -
|
|
11509
|
-
* the normal state of someone who just swapped everything INTO WETH - pays
|
|
11510
|
-
* for it out of the WETH. The deposit then asked for ~2e12 wei more than was
|
|
11511
|
-
* left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
|
|
11512
|
-
* the funds were safe but unadded (ROUT-242).
|
|
11513
|
-
*
|
|
11514
|
-
* So the approval happens here, before the amount is settled, and the amount
|
|
11515
|
-
* comes from a read taken after it. Two things this deliberately is not:
|
|
11516
|
-
*
|
|
11517
|
-
* - It is not a balance sweep. `received` is a DELTA over the pre-swap
|
|
11518
|
-
* balance, so a wallet already holding WETH reads far above it; depositing
|
|
11519
|
-
* the balance would move funds the user never offered. The arrival is the
|
|
11520
|
-
* ceiling and the balance is only ever allowed to lower it.
|
|
11521
|
-
* - It does not touch USDC, which moves on signed typed data and so spends
|
|
11522
|
-
* nothing of the user's in between, nor a paymaster-configured client,
|
|
11523
|
-
* 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.
|
|
11524
12008
|
*/
|
|
11525
|
-
async settleSwapDepositAmount(chainId, token,
|
|
11526
|
-
if (
|
|
12009
|
+
async settleSwapDepositAmount(chainId, token, symbol2, received) {
|
|
12010
|
+
if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
|
|
11527
12011
|
return received.toString();
|
|
11528
12012
|
}
|
|
11529
12013
|
const allowance = await this.readTokenPermit2Allowance(chainId, token);
|
|
11530
|
-
if (allowance < received)
|
|
12014
|
+
if (allowance < received) {
|
|
12015
|
+
await this.approvePermit2(symbol2, received, chainId);
|
|
12016
|
+
}
|
|
11531
12017
|
const balance = await this.readTokenBalance(chainId, token);
|
|
11532
12018
|
return (balance < received ? balance : received).toString();
|
|
11533
12019
|
}
|
|
@@ -11654,21 +12140,30 @@ var OwneySDK = class {
|
|
|
11654
12140
|
* Every order is attempted even if an earlier one throws: they are unrelated
|
|
11655
12141
|
* escrows, and one ending badly must not abandon the next one's secrets.
|
|
11656
12142
|
* `unfinished` is not a verdict — the order may still settle, so unless the
|
|
11657
|
-
*
|
|
12143
|
+
* provider has confirmed full settlement or refund, its preimages are kept.
|
|
11658
12144
|
*/
|
|
11659
12145
|
async resumePendingSwaps(options = {}) {
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
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));
|
|
11665
12159
|
const runner = this.swapRunnerDeps();
|
|
11666
|
-
const
|
|
11667
|
-
|
|
12160
|
+
const sameChain = this.resumeSameChainSwaps(options);
|
|
12161
|
+
const results = await mapConcurrent(pending, async (order) => {
|
|
11668
12162
|
try {
|
|
11669
12163
|
await runFusionOrder(runner, {
|
|
11670
12164
|
orderHash: order.orderHash,
|
|
11671
12165
|
secrets: order.secrets,
|
|
12166
|
+
hasPartialFill: listWithdrawSwaps(Date.now(), true).some((record2) => record2.orderHash === order.orderHash && record2.status === "partially-filled"),
|
|
11672
12167
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11673
12168
|
...options.onStage ? {
|
|
11674
12169
|
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
@@ -11676,25 +12171,23 @@ var OwneySDK = class {
|
|
|
11676
12171
|
});
|
|
11677
12172
|
clearOrder(order.orderHash);
|
|
11678
12173
|
updateWithdrawSwap(order.orderHash, { status: "landed" });
|
|
11679
|
-
|
|
11680
|
-
orderHash: order.orderHash,
|
|
11681
|
-
status: "completed"
|
|
11682
|
-
});
|
|
12174
|
+
return { orderHash: order.orderHash, status: "completed" };
|
|
11683
12175
|
} catch (error) {
|
|
11684
|
-
if (error instanceof OwneyError &&
|
|
12176
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
11685
12177
|
clearOrder(order.orderHash);
|
|
11686
12178
|
updateWithdrawSwap(order.orderHash, { status: "refunded" });
|
|
11687
12179
|
} else {
|
|
11688
|
-
|
|
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" });
|
|
11689
12182
|
}
|
|
11690
|
-
|
|
12183
|
+
return {
|
|
11691
12184
|
orderHash: order.orderHash,
|
|
11692
12185
|
status: "unfinished",
|
|
11693
12186
|
...error instanceof OwneyError ? { error } : {}
|
|
11694
|
-
}
|
|
12187
|
+
};
|
|
11695
12188
|
}
|
|
11696
|
-
}
|
|
11697
|
-
await
|
|
12189
|
+
});
|
|
12190
|
+
await sameChain;
|
|
11698
12191
|
return results;
|
|
11699
12192
|
}
|
|
11700
12193
|
/**
|
|
@@ -11715,34 +12208,32 @@ var OwneySDK = class {
|
|
|
11715
12208
|
* anything else.
|
|
11716
12209
|
*/
|
|
11717
12210
|
async resumeSameChainSwaps(options = {}) {
|
|
11718
|
-
const running = listWithdrawSwaps().filter(
|
|
11719
|
-
|
|
11720
|
-
);
|
|
11721
|
-
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) => {
|
|
11722
12213
|
const runner = {
|
|
11723
12214
|
// The chain bound in. That product keys its orders per chain, and the
|
|
11724
12215
|
// Fusion+ endpoint answers 404 for one — which would read as an order
|
|
11725
12216
|
// that vanished rather than one still in the auction.
|
|
11726
|
-
orderStatus: (h) => this.swapApi().orderStatus(h,
|
|
12217
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, record2.srcChainId),
|
|
11727
12218
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
11728
12219
|
now: () => Date.now()
|
|
11729
12220
|
};
|
|
11730
12221
|
try {
|
|
11731
12222
|
await runSameChainOrder(runner, {
|
|
11732
|
-
orderHash:
|
|
12223
|
+
orderHash: record2.orderHash,
|
|
12224
|
+
hasPartialFill: record2.status === "partially-filled",
|
|
11733
12225
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11734
12226
|
...options.onStage ? {
|
|
11735
|
-
onStage: (stage) => options.onStage(
|
|
12227
|
+
onStage: (stage) => options.onStage(record2.orderHash, stage)
|
|
11736
12228
|
} : {}
|
|
11737
12229
|
});
|
|
11738
|
-
updateWithdrawSwap(
|
|
12230
|
+
updateWithdrawSwap(record2.orderHash, { status: "landed" });
|
|
11739
12231
|
} catch (error) {
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
});
|
|
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" });
|
|
11744
12235
|
}
|
|
11745
|
-
}
|
|
12236
|
+
});
|
|
11746
12237
|
}
|
|
11747
12238
|
/**
|
|
11748
12239
|
* What the wallet actually holds, across every supported chain, restricted
|
|
@@ -11898,6 +12389,92 @@ var OwneySDK = class {
|
|
|
11898
12389
|
);
|
|
11899
12390
|
}
|
|
11900
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
|
+
}
|
|
11901
12478
|
/**
|
|
11902
12479
|
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
11903
12480
|
* to hold, delivered to their own wallet.
|
|
@@ -11926,7 +12503,14 @@ var OwneySDK = class {
|
|
|
11926
12503
|
const state = this.requireState();
|
|
11927
12504
|
const activeChainId = this.requireChainId();
|
|
11928
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;
|
|
11929
12511
|
const assertCurrent = () => {
|
|
12512
|
+
options.assertCurrent?.();
|
|
12513
|
+
if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
|
|
11930
12514
|
if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
|
|
11931
12515
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
|
|
11932
12516
|
};
|
|
@@ -11968,30 +12552,95 @@ var OwneySDK = class {
|
|
|
11968
12552
|
functionName: "balanceOf",
|
|
11969
12553
|
args: [state.walletAddress]
|
|
11970
12554
|
});
|
|
11971
|
-
const
|
|
11972
|
-
|
|
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);
|
|
11973
12565
|
assertCurrent();
|
|
11974
12566
|
debugLog("owney-sdk", "withdrawAndSwap: baseline", {
|
|
11975
12567
|
asset: `${asset.symbol}@${asset.chainId}`,
|
|
11976
12568
|
baseline: baseline.toString()
|
|
11977
12569
|
});
|
|
11978
12570
|
options.onSwapProgress?.("withdrawing");
|
|
11979
|
-
const submittedAt = Date.now();
|
|
11980
|
-
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
|
+
};
|
|
11981
12609
|
const evidence = /* @__PURE__ */ new Map();
|
|
11982
12610
|
const onProgress = (next) => {
|
|
11983
12611
|
if (next.chainId !== activeChainId || next.token !== asset.symbol || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
|
|
11984
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
|
+
}
|
|
11985
12624
|
notifyFunding(options.onWithdrawalProgress, next);
|
|
11986
12625
|
};
|
|
11987
12626
|
let withdraw;
|
|
11988
12627
|
try {
|
|
11989
|
-
withdraw = await this.withdraw({
|
|
12628
|
+
withdraw = saved ? { agentResult: saved.recovery.results, totalWithdrawn: null } : await bounded(() => this.withdraw({
|
|
11990
12629
|
asset: asset.symbol,
|
|
11991
12630
|
...requested === void 0 ? {} : { amount: requested.toString() },
|
|
11992
12631
|
...options.agentId ? { agentId: options.agentId } : {},
|
|
11993
|
-
onProgress
|
|
11994
|
-
|
|
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
|
+
));
|
|
11995
12644
|
} catch (error) {
|
|
11996
12645
|
const details = error instanceof OwneyError ? error.details : void 0;
|
|
11997
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));
|
|
@@ -12013,6 +12662,9 @@ var OwneySDK = class {
|
|
|
12013
12662
|
const results = "agentResult" in withdraw ? withdraw.agentResult : {
|
|
12014
12663
|
[options.agentId ?? withdraw.evidence?.agentId ?? "unknown"]: withdraw
|
|
12015
12664
|
};
|
|
12665
|
+
record2.recovery.results = results;
|
|
12666
|
+
record2.recovery.phase = "verifying";
|
|
12667
|
+
saveWithdrawSwap(record2);
|
|
12016
12668
|
const arrived = await awaitWithdrawalPayouts({
|
|
12017
12669
|
results,
|
|
12018
12670
|
requested,
|
|
@@ -12024,7 +12676,8 @@ var OwneySDK = class {
|
|
|
12024
12676
|
baselineBlock,
|
|
12025
12677
|
assertCurrent,
|
|
12026
12678
|
readBalance: readWalletBalance,
|
|
12027
|
-
|
|
12679
|
+
deadline: confirmationDeadline,
|
|
12680
|
+
verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
|
|
12028
12681
|
inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
|
|
12029
12682
|
resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt }),
|
|
12030
12683
|
history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
|
|
@@ -12032,34 +12685,89 @@ var OwneySDK = class {
|
|
|
12032
12685
|
});
|
|
12033
12686
|
assertCurrent();
|
|
12034
12687
|
const withdrawn = arrived.toString();
|
|
12688
|
+
record2.withdrawn = withdrawn;
|
|
12689
|
+
record2.status = "ready";
|
|
12690
|
+
record2.recovery.phase = "ready";
|
|
12691
|
+
hadDurableRecord = saveWithdrawSwap(record2) || hadDurableRecord;
|
|
12035
12692
|
options.onSwapProgress?.("withdrawn");
|
|
12036
12693
|
try {
|
|
12037
|
-
const quote = await this.swapApi().quote({
|
|
12694
|
+
const quote = await bounded(() => this.swapApi().quote({
|
|
12038
12695
|
from: { ...options.from, amount: withdrawn },
|
|
12039
12696
|
to: options.to,
|
|
12040
12697
|
direction: "withdraw",
|
|
12041
12698
|
walletAddress: state.walletAddress
|
|
12042
|
-
});
|
|
12699
|
+
}), 2e4, () => new OwneyError("SWAP_REQUEST_FAILED", "Swap pricing timed out.", { outcome: "not-submitted" }));
|
|
12043
12700
|
assertCurrent();
|
|
12044
|
-
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
|
+
);
|
|
12045
12706
|
assertCurrent();
|
|
12046
|
-
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,
|
|
12047
12720
|
quote,
|
|
12048
12721
|
walletAddress: state.walletAddress,
|
|
12049
12722
|
direction: "withdraw",
|
|
12050
12723
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
12051
12724
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
|
|
12052
|
-
|
|
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) => {
|
|
12053
12743
|
releaseSubmission();
|
|
12054
|
-
|
|
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
|
+
});
|
|
12055
12756
|
}
|
|
12056
12757
|
});
|
|
12758
|
+
record2.status = "landed";
|
|
12759
|
+
record2.received = swap.received;
|
|
12760
|
+
record2.spent = swap.spent;
|
|
12761
|
+
saveWithdrawSwap(record2);
|
|
12057
12762
|
return { withdraw, withdrawn, swap };
|
|
12058
12763
|
} catch (error) {
|
|
12059
12764
|
throw new OwneyError(
|
|
12060
12765
|
"WITHDRAW_SWAP_FAILED",
|
|
12061
|
-
`
|
|
12766
|
+
`The withdrawal completed, but the swap to ${options.to.symbol} could not be confirmed. Check the order and both balances before retrying.`,
|
|
12062
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 } : {},
|
|
12063
12771
|
withdrawn,
|
|
12064
12772
|
asset: asset.symbol,
|
|
12065
12773
|
chainId: asset.chainId,
|
|
@@ -12070,7 +12778,25 @@ var OwneySDK = class {
|
|
|
12070
12778
|
}
|
|
12071
12779
|
);
|
|
12072
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;
|
|
12073
12798
|
} finally {
|
|
12799
|
+
active2 = false;
|
|
12074
12800
|
releaseSubmission();
|
|
12075
12801
|
}
|
|
12076
12802
|
}
|
|
@@ -12103,7 +12829,15 @@ var OwneySDK = class {
|
|
|
12103
12829
|
const attempted = /* @__PURE__ */ new Set();
|
|
12104
12830
|
this.withdrawalSubmissions.add(submissionKey);
|
|
12105
12831
|
try {
|
|
12106
|
-
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
|
+
);
|
|
12107
12841
|
} catch (error) {
|
|
12108
12842
|
for (const id of ids) if (!attempted.has(id)) {
|
|
12109
12843
|
notifyFunding(options.onProgress, {
|
|
@@ -12193,7 +12927,7 @@ var OwneySDK = class {
|
|
|
12193
12927
|
progress(evidence);
|
|
12194
12928
|
adapterReported = false;
|
|
12195
12929
|
try {
|
|
12196
|
-
if (options.onBeforeSubmission)
|
|
12930
|
+
if (options.onBeforeSubmission || options.assertCurrent)
|
|
12197
12931
|
return await agent.withdraw(
|
|
12198
12932
|
state,
|
|
12199
12933
|
chainId,
|
|
@@ -12201,7 +12935,8 @@ var OwneySDK = class {
|
|
|
12201
12935
|
requested2,
|
|
12202
12936
|
progress,
|
|
12203
12937
|
options.gasMode ?? "auto",
|
|
12204
|
-
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
12938
|
+
options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
|
|
12939
|
+
options.assertCurrent
|
|
12205
12940
|
);
|
|
12206
12941
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
12207
12942
|
} catch (error) {
|
|
@@ -12325,7 +13060,7 @@ var OwneySDK = class {
|
|
|
12325
13060
|
if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
|
|
12326
13061
|
if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
|
|
12327
13062
|
const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
|
|
12328
|
-
if (!total ||
|
|
13063
|
+
if (!total || parseUnits4(total.amount, assetInfo.decimals) <= entry.balance) return;
|
|
12329
13064
|
}
|
|
12330
13065
|
try {
|
|
12331
13066
|
const otherAvailable = indexedAvailable - entry.balance;
|