@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.cjs
CHANGED
|
@@ -39,8 +39,216 @@ __export(index_exports, {
|
|
|
39
39
|
});
|
|
40
40
|
module.exports = __toCommonJS(index_exports);
|
|
41
41
|
|
|
42
|
-
// src/
|
|
42
|
+
// src/errors.ts
|
|
43
|
+
var OwneyError = class extends Error {
|
|
44
|
+
code;
|
|
45
|
+
details;
|
|
46
|
+
agentId;
|
|
47
|
+
constructor(code, message, details, agentId) {
|
|
48
|
+
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
49
|
+
super(`${prefix} ${message}`);
|
|
50
|
+
this.name = "OwneyError";
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.details = details;
|
|
53
|
+
this.agentId = agentId;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var AgentNotFoundError = class extends OwneyError {
|
|
57
|
+
constructor(agentId, available) {
|
|
58
|
+
super(
|
|
59
|
+
"AGENT_NOT_FOUND",
|
|
60
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
61
|
+
{ agentId, available },
|
|
62
|
+
agentId
|
|
63
|
+
);
|
|
64
|
+
this.name = "AgentNotFoundError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var NotConnectedError = class extends OwneyError {
|
|
68
|
+
constructor() {
|
|
69
|
+
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
70
|
+
this.name = "NotConnectedError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var AgentChainIncompatibleError = class extends OwneyError {
|
|
74
|
+
incompatibleAgents;
|
|
75
|
+
connectedChainId;
|
|
76
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
77
|
+
const details = incompatibleAgents.map(
|
|
78
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
79
|
+
).join("; ");
|
|
80
|
+
super(
|
|
81
|
+
"AGENT_CHAIN_INCOMPATIBLE",
|
|
82
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
83
|
+
{ incompatibleAgents, connectedChainId }
|
|
84
|
+
);
|
|
85
|
+
this.name = "AgentChainIncompatibleError";
|
|
86
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
87
|
+
this.connectedChainId = connectedChainId;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/lib/swap/swap.observation.ts
|
|
92
|
+
var active = /* @__PURE__ */ new Set();
|
|
93
|
+
var isOrderObserved = (key2) => active.has(key2);
|
|
94
|
+
async function observeOrder(key2, run) {
|
|
95
|
+
const busy = () => new OwneyError(
|
|
96
|
+
"SWAP_REQUEST_FAILED",
|
|
97
|
+
"This order is already being observed.",
|
|
98
|
+
{ orderHash: key2, watchActive: true, settlementUnconfirmed: true }
|
|
99
|
+
);
|
|
100
|
+
if (active.has(key2)) throw busy();
|
|
101
|
+
active.add(key2);
|
|
102
|
+
try {
|
|
103
|
+
if (typeof navigator !== "undefined" && navigator.locks)
|
|
104
|
+
return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
|
|
105
|
+
if (!lock) throw busy();
|
|
106
|
+
return run();
|
|
107
|
+
});
|
|
108
|
+
return await run();
|
|
109
|
+
} finally {
|
|
110
|
+
active.delete(key2);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/lib/swap/swap.deadline.ts
|
|
115
|
+
async function bounded(work, timeoutMs, error) {
|
|
116
|
+
if (timeoutMs <= 0) throw error();
|
|
117
|
+
let timer;
|
|
118
|
+
try {
|
|
119
|
+
return await Promise.race([
|
|
120
|
+
Promise.resolve().then(work),
|
|
121
|
+
new Promise((_, reject) => {
|
|
122
|
+
timer = setTimeout(() => reject(error()), timeoutMs);
|
|
123
|
+
})
|
|
124
|
+
]);
|
|
125
|
+
} finally {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function mapConcurrent(items, run, concurrency = 3) {
|
|
130
|
+
const results = new Array(items.length);
|
|
131
|
+
let next = 0;
|
|
132
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
133
|
+
for (; ; ) {
|
|
134
|
+
const index = next++;
|
|
135
|
+
if (index >= items.length) return;
|
|
136
|
+
results[index] = await run(items[index]);
|
|
137
|
+
}
|
|
138
|
+
}));
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/lib/permit2-batch.ts
|
|
43
143
|
var import_viem = require("viem");
|
|
144
|
+
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
145
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
146
|
+
var ARBITRUM_USDC_ADDRESS = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
|
|
147
|
+
function supportsSponsoredErc2612(chainId, token) {
|
|
148
|
+
const expected = chainId === 8453 ? BASE_USDC_ADDRESS : chainId === 42161 ? ARBITRUM_USDC_ADDRESS : void 0;
|
|
149
|
+
return expected !== void 0 && token.toLowerCase() === expected.toLowerCase();
|
|
150
|
+
}
|
|
151
|
+
var ERC2612_PERMIT_TYPES = {
|
|
152
|
+
Permit: [
|
|
153
|
+
{ name: "owner", type: "address" },
|
|
154
|
+
{ name: "spender", type: "address" },
|
|
155
|
+
{ name: "value", type: "uint256" },
|
|
156
|
+
{ name: "nonce", type: "uint256" },
|
|
157
|
+
{ name: "deadline", type: "uint256" }
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
var ERC2612_READ_ABI = (0, import_viem.parseAbi)([
|
|
161
|
+
"function name() view returns (string)",
|
|
162
|
+
"function version() view returns (string)",
|
|
163
|
+
"function nonces(address owner) view returns (uint256)"
|
|
164
|
+
]);
|
|
165
|
+
function erc2612TypedData(input) {
|
|
166
|
+
return {
|
|
167
|
+
domain: {
|
|
168
|
+
name: input.permit.tokenName,
|
|
169
|
+
version: input.permit.tokenVersion,
|
|
170
|
+
chainId: input.chainId,
|
|
171
|
+
verifyingContract: input.token
|
|
172
|
+
},
|
|
173
|
+
types: ERC2612_PERMIT_TYPES,
|
|
174
|
+
primaryType: "Permit",
|
|
175
|
+
message: {
|
|
176
|
+
owner: input.owner,
|
|
177
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
178
|
+
value: BigInt(input.permit.value),
|
|
179
|
+
nonce: BigInt(input.permit.nonce),
|
|
180
|
+
deadline: BigInt(input.permit.deadline)
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
var PERMIT_BATCH_TYPES = {
|
|
185
|
+
PermitBatchWitnessTransferFrom: [
|
|
186
|
+
{ name: "permitted", type: "TokenPermissions[]" },
|
|
187
|
+
{ name: "spender", type: "address" },
|
|
188
|
+
{ name: "nonce", type: "uint256" },
|
|
189
|
+
{ name: "deadline", type: "uint256" },
|
|
190
|
+
{ name: "witness", type: "Deposit" }
|
|
191
|
+
],
|
|
192
|
+
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
193
|
+
TokenPermissions: [
|
|
194
|
+
{ name: "token", type: "address" },
|
|
195
|
+
{ name: "amount", type: "uint256" }
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
var PERMIT2_BATCH_ABI = (0, import_viem.parseAbi)([
|
|
199
|
+
"struct TokenPermissions { address token; uint256 amount; }",
|
|
200
|
+
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
201
|
+
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
202
|
+
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
203
|
+
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
204
|
+
]);
|
|
205
|
+
function batchPermit(b) {
|
|
206
|
+
return {
|
|
207
|
+
permitted: b.transfers.map((t) => ({
|
|
208
|
+
token: b.token,
|
|
209
|
+
amount: BigInt(t.amount)
|
|
210
|
+
})),
|
|
211
|
+
nonce: BigInt(b.nonce),
|
|
212
|
+
deadline: BigInt(b.deadline)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function batchTypedData(b, spender) {
|
|
216
|
+
return {
|
|
217
|
+
domain: {
|
|
218
|
+
name: "Permit2",
|
|
219
|
+
chainId: b.chainId,
|
|
220
|
+
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
221
|
+
},
|
|
222
|
+
types: PERMIT_BATCH_TYPES,
|
|
223
|
+
primaryType: "PermitBatchWitnessTransferFrom",
|
|
224
|
+
message: {
|
|
225
|
+
...batchPermit(b),
|
|
226
|
+
spender,
|
|
227
|
+
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
232
|
+
function batchWitness(b) {
|
|
233
|
+
return (0, import_viem.hashStruct)({
|
|
234
|
+
types: PERMIT_BATCH_TYPES,
|
|
235
|
+
primaryType: "Deposit",
|
|
236
|
+
data: { recipients: b.transfers.map((t) => t.to) }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function batchArgs(b) {
|
|
240
|
+
return [
|
|
241
|
+
batchPermit(b),
|
|
242
|
+
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
243
|
+
b.from,
|
|
244
|
+
batchWitness(b),
|
|
245
|
+
BATCH_WITNESS_TYPE,
|
|
246
|
+
b.signature
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/lib/funding-evidence.ts
|
|
251
|
+
var import_viem2 = require("viem");
|
|
44
252
|
var import_account_abstraction = require("viem/account-abstraction");
|
|
45
253
|
function notifyFunding(callback, evidence) {
|
|
46
254
|
try {
|
|
@@ -49,9 +257,9 @@ function notifyFunding(callback, evidence) {
|
|
|
49
257
|
} catch {
|
|
50
258
|
}
|
|
51
259
|
}
|
|
52
|
-
var fullWithdrawalAbi = (0,
|
|
53
|
-
var withdrawalEventAbi = (0,
|
|
54
|
-
var accountExecutionAbi = (0,
|
|
260
|
+
var fullWithdrawalAbi = (0, import_viem2.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
261
|
+
var withdrawalEventAbi = (0, import_viem2.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
262
|
+
var accountExecutionAbi = (0, import_viem2.parseAbi)([
|
|
55
263
|
"function execute(address target,uint256 value,bytes data)",
|
|
56
264
|
"function executeBatch((address target,uint256 value,bytes data)[] calls)"
|
|
57
265
|
]);
|
|
@@ -61,7 +269,7 @@ function yieldseekerExecution(receipt, evidence) {
|
|
|
61
269
|
const operationEvents = receipt.logs.flatMap((log) => {
|
|
62
270
|
if (![entryPoint06, entryPoint07].includes(log.address.toLowerCase())) return [];
|
|
63
271
|
try {
|
|
64
|
-
const event = (0,
|
|
272
|
+
const event = (0, import_viem2.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
|
|
65
273
|
return event.eventName === "UserOperationEvent" ? [{ ...event.args, logIndex: log.logIndex }] : [];
|
|
66
274
|
} catch {
|
|
67
275
|
return [];
|
|
@@ -84,7 +292,7 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
|
|
|
84
292
|
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
|
|
85
293
|
const matchesFullCall = (data) => {
|
|
86
294
|
try {
|
|
87
|
-
const decoded = (0,
|
|
295
|
+
const decoded = (0, import_viem2.decodeFunctionData)({ abi: fullWithdrawalAbi, data });
|
|
88
296
|
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
89
297
|
} catch {
|
|
90
298
|
return false;
|
|
@@ -95,7 +303,7 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
|
|
|
95
303
|
const address = transaction.to?.toLowerCase();
|
|
96
304
|
if (address !== entryPoint06 && address !== entryPoint07) return false;
|
|
97
305
|
const version = address === entryPoint06 ? "0.6" : "0.7";
|
|
98
|
-
const decoded = (0,
|
|
306
|
+
const decoded = (0, import_viem2.decodeFunctionData)({ abi: version === "0.6" ? import_account_abstraction.entryPoint06Abi : import_account_abstraction.entryPoint07Abi, data: transaction.input });
|
|
99
307
|
if (decoded.functionName !== "handleOps") return false;
|
|
100
308
|
const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
|
|
101
309
|
if (operations.length !== 1) return false;
|
|
@@ -111,14 +319,14 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
|
|
|
111
319
|
if (!receipt?.logs.some((log) => {
|
|
112
320
|
if (log.address.toLowerCase() !== address) return false;
|
|
113
321
|
try {
|
|
114
|
-
const event = (0,
|
|
322
|
+
const event = (0, import_viem2.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
|
|
115
323
|
return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash2.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
|
|
116
324
|
} catch {
|
|
117
325
|
return false;
|
|
118
326
|
}
|
|
119
327
|
})) return false;
|
|
120
328
|
if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
|
|
121
|
-
const execution = (0,
|
|
329
|
+
const execution = (0, import_viem2.decodeFunctionData)({ abi: accountExecutionAbi, data: operation.callData });
|
|
122
330
|
const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
|
|
123
331
|
return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
|
|
124
332
|
} catch {
|
|
@@ -161,7 +369,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, optio
|
|
|
161
369
|
if (seen.has(log.logIndex)) continue;
|
|
162
370
|
seen.add(log.logIndex);
|
|
163
371
|
try {
|
|
164
|
-
const event = (0,
|
|
372
|
+
const event = (0, import_viem2.decodeEventLog)({ abi: import_viem2.erc20Abi, data: log.data, topics: log.topics });
|
|
165
373
|
if (event.eventName !== "Transfer") continue;
|
|
166
374
|
const { from, to, value } = event.args;
|
|
167
375
|
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
@@ -185,7 +393,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, optio
|
|
|
185
393
|
const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && execution.logs.some((log) => {
|
|
186
394
|
if (log.address.toLowerCase() !== agent) return false;
|
|
187
395
|
try {
|
|
188
|
-
const { args } = (0,
|
|
396
|
+
const { args } = (0, import_viem2.decodeEventLog)({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
|
|
189
397
|
return args.owner.toLowerCase() === owner && args.recipient.toLowerCase() === owner && args.token.toLowerCase() === tokenAddress.toLowerCase() && args.amount === 0n;
|
|
190
398
|
} catch {
|
|
191
399
|
return false;
|
|
@@ -244,57 +452,8 @@ function registerDepositBatch(callback, transfer) {
|
|
|
244
452
|
batchCallbacks.set(callback, transfer);
|
|
245
453
|
}
|
|
246
454
|
|
|
247
|
-
// src/errors.ts
|
|
248
|
-
var OwneyError = class extends Error {
|
|
249
|
-
code;
|
|
250
|
-
details;
|
|
251
|
-
agentId;
|
|
252
|
-
constructor(code, message, details, agentId) {
|
|
253
|
-
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
254
|
-
super(`${prefix} ${message}`);
|
|
255
|
-
this.name = "OwneyError";
|
|
256
|
-
this.code = code;
|
|
257
|
-
this.details = details;
|
|
258
|
-
this.agentId = agentId;
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
var AgentNotFoundError = class extends OwneyError {
|
|
262
|
-
constructor(agentId, available) {
|
|
263
|
-
super(
|
|
264
|
-
"AGENT_NOT_FOUND",
|
|
265
|
-
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
266
|
-
{ agentId, available },
|
|
267
|
-
agentId
|
|
268
|
-
);
|
|
269
|
-
this.name = "AgentNotFoundError";
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
var NotConnectedError = class extends OwneyError {
|
|
273
|
-
constructor() {
|
|
274
|
-
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
275
|
-
this.name = "NotConnectedError";
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
var AgentChainIncompatibleError = class extends OwneyError {
|
|
279
|
-
incompatibleAgents;
|
|
280
|
-
connectedChainId;
|
|
281
|
-
constructor(incompatibleAgents, connectedChainId) {
|
|
282
|
-
const details = incompatibleAgents.map(
|
|
283
|
-
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
284
|
-
).join("; ");
|
|
285
|
-
super(
|
|
286
|
-
"AGENT_CHAIN_INCOMPATIBLE",
|
|
287
|
-
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
288
|
-
{ incompatibleAgents, connectedChainId }
|
|
289
|
-
);
|
|
290
|
-
this.name = "AgentChainIncompatibleError";
|
|
291
|
-
this.incompatibleAgents = incompatibleAgents;
|
|
292
|
-
this.connectedChainId = connectedChainId;
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
455
|
// src/lib/transfer-auth.ts
|
|
297
|
-
var
|
|
456
|
+
var import_viem3 = require("viem");
|
|
298
457
|
|
|
299
458
|
// src/lib/sponsor-client.ts
|
|
300
459
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
@@ -410,7 +569,7 @@ async function postSponsorBatchTransfer(input) {
|
|
|
410
569
|
}
|
|
411
570
|
|
|
412
571
|
// src/lib/permit2.ts
|
|
413
|
-
var
|
|
572
|
+
var import_viem4 = require("viem");
|
|
414
573
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
415
574
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
416
575
|
function permit2ApprovalAmount(requiredAmount) {
|
|
@@ -451,7 +610,7 @@ var ERC20_ALLOWANCE_ABI = [
|
|
|
451
610
|
function randomPermit2Nonce() {
|
|
452
611
|
const bytes = new Uint8Array(32);
|
|
453
612
|
globalThis.crypto.getRandomValues(bytes);
|
|
454
|
-
return BigInt((0,
|
|
613
|
+
return BigInt((0, import_viem4.bytesToHex)(bytes));
|
|
455
614
|
}
|
|
456
615
|
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
457
616
|
return publicClient.readContract({
|
|
@@ -559,21 +718,21 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
559
718
|
}
|
|
560
719
|
if (typeof value !== "object" || seen.has(value)) return;
|
|
561
720
|
seen.add(value);
|
|
562
|
-
const
|
|
563
|
-
if ([
|
|
721
|
+
const record2 = value;
|
|
722
|
+
if ([record2.status, record2.statusCode, record2.code].some(
|
|
564
723
|
(code) => String(code) === "429"
|
|
565
724
|
)) {
|
|
566
725
|
limited = true;
|
|
567
726
|
}
|
|
568
727
|
for (const key2 of ["retryAfterSeconds", "retryAfter"]) {
|
|
569
|
-
const seconds = Number(
|
|
728
|
+
const seconds = Number(record2[key2]);
|
|
570
729
|
if (Number.isFinite(seconds) && seconds > 0)
|
|
571
730
|
delay = Math.max(delay, seconds * 1e3);
|
|
572
731
|
}
|
|
573
|
-
const retryAt = Number(
|
|
732
|
+
const retryAt = Number(record2.retryAt);
|
|
574
733
|
if (Number.isFinite(retryAt) && retryAt > now)
|
|
575
734
|
delay = Math.max(delay, retryAt - now);
|
|
576
|
-
const headers =
|
|
735
|
+
const headers = record2.headers;
|
|
577
736
|
const header = typeof headers?.get === "function" ? headers.get("Retry-After") : headers?.["retry-after"] ?? headers?.["Retry-After"];
|
|
578
737
|
if (typeof header === "string" || typeof header === "number") {
|
|
579
738
|
const seconds = Number(header);
|
|
@@ -589,10 +748,10 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
589
748
|
"data",
|
|
590
749
|
"fields"
|
|
591
750
|
]) {
|
|
592
|
-
visit(
|
|
751
|
+
visit(record2[key2], depth + 1);
|
|
593
752
|
}
|
|
594
753
|
for (const key2 of ["agentErrors", "failures"]) {
|
|
595
|
-
const entries =
|
|
754
|
+
const entries = record2[key2];
|
|
596
755
|
if (entries && typeof entries === "object") {
|
|
597
756
|
for (const entry of Object.values(entries)) visit(entry, depth + 1);
|
|
598
757
|
}
|
|
@@ -610,7 +769,7 @@ function rateLimitDiagnostics(error) {
|
|
|
610
769
|
if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
|
|
611
770
|
return;
|
|
612
771
|
seen.add(value);
|
|
613
|
-
const
|
|
772
|
+
const record2 = value;
|
|
614
773
|
for (const key2 of [
|
|
615
774
|
"rpcSource",
|
|
616
775
|
"chainId",
|
|
@@ -618,12 +777,12 @@ function rateLimitDiagnostics(error) {
|
|
|
618
777
|
"providerRequestId",
|
|
619
778
|
"statusCode"
|
|
620
779
|
]) {
|
|
621
|
-
const candidate =
|
|
780
|
+
const candidate = record2[key2];
|
|
622
781
|
if (candidate !== void 0 && details[key2] === void 0)
|
|
623
782
|
details = { ...details, [key2]: candidate };
|
|
624
783
|
}
|
|
625
784
|
for (const key2 of ["details", "cause", "response", "fields", "error"])
|
|
626
|
-
visit(
|
|
785
|
+
visit(record2[key2], depth + 1);
|
|
627
786
|
}
|
|
628
787
|
visit(error);
|
|
629
788
|
return {
|
|
@@ -691,12 +850,19 @@ var AgentReads = class {
|
|
|
691
850
|
};
|
|
692
851
|
|
|
693
852
|
// src/agents/zyfai/zyfai.withdraw-submission.ts
|
|
853
|
+
var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
|
|
694
854
|
var listenersByClient = /* @__PURE__ */ new WeakMap();
|
|
695
855
|
function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
696
856
|
const client = sdk.httpClient;
|
|
697
857
|
if (!client || typeof client.get !== "function" || typeof client.post !== "function")
|
|
698
|
-
return {
|
|
699
|
-
|
|
858
|
+
return {
|
|
859
|
+
attempted: () => void 0,
|
|
860
|
+
rejected: () => false,
|
|
861
|
+
request: () => void 0,
|
|
862
|
+
belowMinimum: () => void 0,
|
|
863
|
+
stop: () => {
|
|
864
|
+
}
|
|
865
|
+
};
|
|
700
866
|
let listeners2 = listenersByClient.get(client);
|
|
701
867
|
if (!listeners2) {
|
|
702
868
|
listeners2 = /* @__PURE__ */ new Set();
|
|
@@ -716,6 +882,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
716
882
|
method: method === "get" ? "GET" : "POST",
|
|
717
883
|
path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
|
|
718
884
|
};
|
|
885
|
+
let belowMinimum2;
|
|
719
886
|
const notify2 = (outcome) => {
|
|
720
887
|
if (outcome === "rejected" && matching.length !== 1) return;
|
|
721
888
|
const evidence = matching.length === 1 ? { ...request3 } : {
|
|
@@ -726,7 +893,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
726
893
|
};
|
|
727
894
|
for (const observer of matching) {
|
|
728
895
|
try {
|
|
729
|
-
observer.notify(outcome, evidence);
|
|
896
|
+
observer.notify(outcome, evidence, outcome === "rejected" ? belowMinimum2 : void 0);
|
|
730
897
|
} catch {
|
|
731
898
|
}
|
|
732
899
|
}
|
|
@@ -751,10 +918,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
751
918
|
return await original.apply(this, callArgs);
|
|
752
919
|
} catch (error) {
|
|
753
920
|
const message = error instanceof Error ? error.message : "";
|
|
921
|
+
const minimum = BELOW_MINIMUM.exec(message);
|
|
922
|
+
if (minimum) belowMinimum2 = { symbol: minimum[2], idle: minimum[1], minimum: minimum[3] };
|
|
754
923
|
const status = error?.response?.status;
|
|
755
924
|
if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
756
925
|
request3.httpStatus = status;
|
|
757
|
-
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" :
|
|
926
|
+
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";
|
|
758
927
|
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");
|
|
759
928
|
throw error;
|
|
760
929
|
} finally {
|
|
@@ -767,32 +936,45 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
767
936
|
let submitted = false;
|
|
768
937
|
let rejected = false;
|
|
769
938
|
let request2 = { attempted: false };
|
|
770
|
-
|
|
939
|
+
let belowMinimum;
|
|
940
|
+
const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
|
|
771
941
|
request2 = evidence;
|
|
772
942
|
if (outcome === "attempted") submitted = true;
|
|
773
|
-
else if (outcome === "rejected")
|
|
943
|
+
else if (outcome === "rejected") {
|
|
944
|
+
rejected = true;
|
|
945
|
+
belowMinimum = detail;
|
|
946
|
+
}
|
|
774
947
|
} };
|
|
775
948
|
listeners2.add(listener);
|
|
776
|
-
return {
|
|
777
|
-
|
|
778
|
-
|
|
949
|
+
return {
|
|
950
|
+
attempted: () => submitted,
|
|
951
|
+
rejected: () => rejected,
|
|
952
|
+
request: () => ({ ...request2 }),
|
|
953
|
+
belowMinimum: () => belowMinimum && { ...belowMinimum },
|
|
954
|
+
stop: () => {
|
|
955
|
+
listeners2.delete(listener);
|
|
956
|
+
}
|
|
957
|
+
};
|
|
779
958
|
}
|
|
780
959
|
|
|
781
960
|
// src/agents/zyfai/zyfai.withdraw-empty.ts
|
|
782
|
-
|
|
783
|
-
|
|
961
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
962
|
+
var symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
|
|
963
|
+
var chain = (value) => {
|
|
964
|
+
if (typeof value === "number") return value;
|
|
965
|
+
if (typeof value !== "string") return void 0;
|
|
966
|
+
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
967
|
+
};
|
|
968
|
+
var zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
969
|
+
var units = (value) => typeof value === "string" && /^(?:0x[0-9a-f]+|\d+)$/i.test(value) ? BigInt(value) : void 0;
|
|
970
|
+
function snapshotPortfolio(raw2) {
|
|
784
971
|
const response = record(raw2);
|
|
785
972
|
const portfolio = record(response?.portfolio);
|
|
786
973
|
const chains = record(portfolio?.portfolioByChain);
|
|
787
|
-
if (response?.success === false || !chains || !Array.isArray(portfolio
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (typeof value === "number") return value;
|
|
792
|
-
if (typeof value !== "string") return void 0;
|
|
793
|
-
return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
|
|
794
|
-
};
|
|
795
|
-
const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
|
|
974
|
+
if (response?.success === false || !portfolio || !chains || !Array.isArray(portfolio.positions) || !Array.isArray(portfolio.staleBalances)) return void 0;
|
|
975
|
+
return { portfolio, chains, entries: [...portfolio.positions, ...portfolio.staleBalances] };
|
|
976
|
+
}
|
|
977
|
+
function chainBalancesMatch(chains, chainId, token, accept) {
|
|
796
978
|
for (const [id, assets] of Object.entries(chains)) {
|
|
797
979
|
const idNumber = chain(id);
|
|
798
980
|
if (idNumber === void 0) return false;
|
|
@@ -802,10 +984,13 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
|
802
984
|
for (const [asset, value] of Object.entries(entries)) {
|
|
803
985
|
if (symbol(asset) !== token) continue;
|
|
804
986
|
const balance = record(value);
|
|
805
|
-
if (!balance || !
|
|
987
|
+
if (!balance || !accept(balance)) return false;
|
|
806
988
|
}
|
|
807
989
|
}
|
|
808
|
-
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
function positionsEmpty(entries, chainId, token) {
|
|
993
|
+
for (const value of entries) {
|
|
809
994
|
const entry = record(value);
|
|
810
995
|
if (!entry) return false;
|
|
811
996
|
const id = chain(entry.chainId ?? entry.chain);
|
|
@@ -817,9 +1002,26 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
|
817
1002
|
}
|
|
818
1003
|
return true;
|
|
819
1004
|
}
|
|
1005
|
+
function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
|
|
1006
|
+
const snapshot = snapshotPortfolio(raw2);
|
|
1007
|
+
if (!snapshot) return false;
|
|
1008
|
+
const pending = snapshot.portfolio.pendingAsyncWithdrawals;
|
|
1009
|
+
if (pending !== void 0 && (!Array.isArray(pending) || pending.length > 0)) return false;
|
|
1010
|
+
return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => zero(balance.balance)) && positionsEmpty(snapshot.entries, chainId, token);
|
|
1011
|
+
}
|
|
1012
|
+
function hasIdleOnlyWithdrawalSnapshot(raw2, chainId, token, decimals, idle) {
|
|
1013
|
+
const snapshot = snapshotPortfolio(raw2);
|
|
1014
|
+
if (!snapshot) return false;
|
|
1015
|
+
const pending = snapshot.portfolio.pendingAsyncWithdrawals;
|
|
1016
|
+
if (pending !== void 0 && (!Array.isArray(pending) || pending.some((value) => record(value)?.status !== "CLAIMED"))) return false;
|
|
1017
|
+
return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => {
|
|
1018
|
+
const amount = units(balance.balance);
|
|
1019
|
+
return amount !== void 0 && amount <= idle && (balance.decimals === void 0 || balance.decimals === decimals);
|
|
1020
|
+
}) && positionsEmpty(snapshot.entries, chainId, token);
|
|
1021
|
+
}
|
|
820
1022
|
|
|
821
1023
|
// src/agents/zyfai/zyfai.deposit.ts
|
|
822
|
-
var
|
|
1024
|
+
var import_viem5 = require("viem");
|
|
823
1025
|
|
|
824
1026
|
// src/types/config.ts
|
|
825
1027
|
var P_SUPPORTED_CHAINS = {
|
|
@@ -869,20 +1071,20 @@ var SupportedAssets = [
|
|
|
869
1071
|
];
|
|
870
1072
|
|
|
871
1073
|
// src/agents/zyfai/zyfai.deposit.ts
|
|
872
|
-
async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
|
|
1074
|
+
async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
|
|
873
1075
|
const owner = state.walletAddress;
|
|
874
1076
|
const provider = state.provider;
|
|
875
|
-
const token = SupportedAssets.find((value) => value.chainId ===
|
|
876
|
-
if (!token || !(0,
|
|
877
|
-
throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId:
|
|
1077
|
+
const token = SupportedAssets.find((value) => value.chainId === chain2.id && value.symbol === asset);
|
|
1078
|
+
if (!token || !(0, import_viem5.isAddress)(owner) || !(0, import_viem5.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
|
|
1079
|
+
throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
|
|
878
1080
|
assertCurrent();
|
|
879
|
-
const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId:
|
|
1081
|
+
const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain2.id, amount, asset, strategy: "aggressive" });
|
|
880
1082
|
assertCurrent();
|
|
881
|
-
const expectedData = (0,
|
|
1083
|
+
const expectedData = (0, import_viem5.encodeFunctionData)({ abi: import_viem5.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
|
|
882
1084
|
const transfer = prepared?.transfer;
|
|
883
1085
|
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")
|
|
884
|
-
throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId:
|
|
885
|
-
const wallet = (0,
|
|
1086
|
+
throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
|
|
1087
|
+
const wallet = (0, import_viem5.createWalletClient)({ account: owner, chain: chain2, transport: (0, import_viem5.custom)({
|
|
886
1088
|
request: async (args) => {
|
|
887
1089
|
assertCurrent();
|
|
888
1090
|
if (args.method === "eth_sendTransaction") {
|
|
@@ -891,7 +1093,7 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
|
|
|
891
1093
|
provider.request({ method: "eth_chainId" })
|
|
892
1094
|
]);
|
|
893
1095
|
assertCurrent();
|
|
894
|
-
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !==
|
|
1096
|
+
if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain2.id)
|
|
895
1097
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, "zyfai");
|
|
896
1098
|
await onBeforeSubmission?.();
|
|
897
1099
|
assertCurrent();
|
|
@@ -899,12 +1101,12 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
|
|
|
899
1101
|
return provider.request(args);
|
|
900
1102
|
}
|
|
901
1103
|
}, { retryCount: 0 }) });
|
|
902
|
-
return wallet.sendTransaction({ account: owner, chain, to: token.address, data: expectedData, value: 0n });
|
|
1104
|
+
return wallet.sendTransaction({ account: owner, chain: chain2, to: token.address, data: expectedData, value: 0n });
|
|
903
1105
|
}
|
|
904
1106
|
|
|
905
1107
|
// src/agents/zyfai/zyfai.agent.ts
|
|
906
1108
|
var import_sdk = require("@zyfai/sdk");
|
|
907
|
-
var
|
|
1109
|
+
var import_viem8 = require("viem");
|
|
908
1110
|
var import_chains = require("viem/chains");
|
|
909
1111
|
|
|
910
1112
|
// src/lib/debug.ts
|
|
@@ -955,13 +1157,13 @@ function assetAliases(asset) {
|
|
|
955
1157
|
const normalised = asset.toLowerCase();
|
|
956
1158
|
return normalised === "weth" ? ["weth", "eth"] : [normalised];
|
|
957
1159
|
}
|
|
958
|
-
function matchesAsset(
|
|
959
|
-
return assetAliases(asset).includes(
|
|
1160
|
+
function matchesAsset(symbol2, asset) {
|
|
1161
|
+
return assetAliases(asset).includes(symbol2.toLowerCase());
|
|
960
1162
|
}
|
|
961
1163
|
|
|
962
1164
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
963
|
-
var
|
|
964
|
-
var exactHexAmount = (raw2, decimals) => (0,
|
|
1165
|
+
var import_viem6 = require("viem");
|
|
1166
|
+
var exactHexAmount = (raw2, decimals) => (0, import_viem6.formatUnits)(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
|
|
965
1167
|
var DECIMALS_BY_SYMBOL = {
|
|
966
1168
|
usdc: 6,
|
|
967
1169
|
weth: 18,
|
|
@@ -970,10 +1172,10 @@ var DECIMALS_BY_SYMBOL = {
|
|
|
970
1172
|
wbtc: 8,
|
|
971
1173
|
cbbtc: 8
|
|
972
1174
|
};
|
|
973
|
-
function tokenDecimals(
|
|
1175
|
+
function tokenDecimals(symbol2, explicit) {
|
|
974
1176
|
if (typeof explicit === "number" && Number.isFinite(explicit))
|
|
975
1177
|
return explicit;
|
|
976
|
-
return DECIMALS_BY_SYMBOL[(
|
|
1178
|
+
return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
|
|
977
1179
|
}
|
|
978
1180
|
function mapWithdraw(raw2) {
|
|
979
1181
|
return {
|
|
@@ -993,10 +1195,10 @@ var NAME_TO_CHAIN_ID = Object.entries(
|
|
|
993
1195
|
acc[name.toLowerCase()] = Number(id);
|
|
994
1196
|
return acc;
|
|
995
1197
|
}, {});
|
|
996
|
-
function resolveChainId(
|
|
997
|
-
const asNum = Number(
|
|
1198
|
+
function resolveChainId(chain2) {
|
|
1199
|
+
const asNum = Number(chain2);
|
|
998
1200
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
999
|
-
return NAME_TO_CHAIN_ID[
|
|
1201
|
+
return NAME_TO_CHAIN_ID[chain2.trim().toLowerCase()] ?? NaN;
|
|
1000
1202
|
}
|
|
1001
1203
|
function mapPendingAllocations(raw2) {
|
|
1002
1204
|
if (!Array.isArray(raw2)) return void 0;
|
|
@@ -1036,16 +1238,16 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
1036
1238
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
1037
1239
|
const positionChainId = Number(chainIdKey);
|
|
1038
1240
|
for (const [
|
|
1039
|
-
|
|
1241
|
+
symbol2,
|
|
1040
1242
|
{ balance, balanceWithFee, decimals }
|
|
1041
1243
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
1042
1244
|
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
1043
|
-
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(
|
|
1245
|
+
const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol2, decimals));
|
|
1044
1246
|
totalBalance += Number(amount);
|
|
1045
1247
|
tokens.push({
|
|
1046
1248
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
1047
1249
|
chainId: positionChainId,
|
|
1048
|
-
asset:
|
|
1250
|
+
asset: symbol2,
|
|
1049
1251
|
amount
|
|
1050
1252
|
});
|
|
1051
1253
|
}
|
|
@@ -1060,7 +1262,7 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
1060
1262
|
// legacy `amount` fallback is hex. Runtime
|
|
1061
1263
|
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
1062
1264
|
// when present, otherwise fall back to the per-symbol lookup.
|
|
1063
|
-
amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0,
|
|
1265
|
+
amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0, import_viem6.formatUnits)(
|
|
1064
1266
|
BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
|
|
1065
1267
|
tokenDecimals(
|
|
1066
1268
|
p.token_symbol,
|
|
@@ -1113,12 +1315,12 @@ function mapEarnings(raw2, smartWallet) {
|
|
|
1113
1315
|
totalEarningsByChain
|
|
1114
1316
|
)) {
|
|
1115
1317
|
const positionChainId = Number(chainIdKey);
|
|
1116
|
-
for (const [
|
|
1318
|
+
for (const [symbol2, earnings] of Object.entries(tokensBySymbol ?? {})) {
|
|
1117
1319
|
const amount = Number(earnings);
|
|
1118
1320
|
tokens.push({
|
|
1119
1321
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
1120
1322
|
chainId: positionChainId,
|
|
1121
|
-
asset:
|
|
1323
|
+
asset: symbol2,
|
|
1122
1324
|
amount: String(amount)
|
|
1123
1325
|
});
|
|
1124
1326
|
}
|
|
@@ -1138,8 +1340,8 @@ function mapWeightedApyByChain(raw2) {
|
|
|
1138
1340
|
const chainId = Number(chainKey);
|
|
1139
1341
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
1140
1342
|
const perAsset = {};
|
|
1141
|
-
for (const [
|
|
1142
|
-
const token =
|
|
1343
|
+
for (const [symbol2, apy] of Object.entries(tokenApy)) {
|
|
1344
|
+
const token = symbol2;
|
|
1143
1345
|
if (!SUPPORTED_TOKENS.includes(token)) continue;
|
|
1144
1346
|
perAsset[token] = apy;
|
|
1145
1347
|
}
|
|
@@ -1370,8 +1572,8 @@ function computeAllocationApy(positions) {
|
|
|
1370
1572
|
function earningsForToken(bucket, chainId, asset) {
|
|
1371
1573
|
const tokens = bucket?.[String(chainId)];
|
|
1372
1574
|
if (!tokens) return null;
|
|
1373
|
-
for (const [
|
|
1374
|
-
if (!matchesAsset(
|
|
1575
|
+
for (const [symbol2, value] of Object.entries(tokens)) {
|
|
1576
|
+
if (!matchesAsset(symbol2, asset)) continue;
|
|
1375
1577
|
const amount = Number(value);
|
|
1376
1578
|
return Number.isFinite(amount) ? amount : null;
|
|
1377
1579
|
}
|
|
@@ -1388,8 +1590,8 @@ function assetsInSnapshot(entry, chainId) {
|
|
|
1388
1590
|
entry.current_earnings_by_token,
|
|
1389
1591
|
entry.total_earnings_by_token
|
|
1390
1592
|
]) {
|
|
1391
|
-
for (const
|
|
1392
|
-
seen.add(
|
|
1593
|
+
for (const symbol2 of Object.keys(bucket?.[key2] ?? {})) {
|
|
1594
|
+
seen.add(symbol2.toUpperCase());
|
|
1393
1595
|
}
|
|
1394
1596
|
}
|
|
1395
1597
|
return [...seen];
|
|
@@ -1724,7 +1926,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1724
1926
|
}
|
|
1725
1927
|
|
|
1726
1928
|
// src/lib/paid-rpc.ts
|
|
1727
|
-
var
|
|
1929
|
+
var import_viem7 = require("viem");
|
|
1728
1930
|
var PAID_RPC_RETRY_COUNT = 3;
|
|
1729
1931
|
var PAID_RPC_RETRY_DELAY_MS = 1e3;
|
|
1730
1932
|
function configuredRpcProxyBaseUrl(baseUrl) {
|
|
@@ -1755,14 +1957,14 @@ function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
|
|
|
1755
1957
|
42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
|
|
1756
1958
|
};
|
|
1757
1959
|
}
|
|
1758
|
-
function createPaidRpcClient(
|
|
1960
|
+
function createPaidRpcClient(chain2, rpcUrls) {
|
|
1759
1961
|
const url = resolveRpcUrl(
|
|
1760
1962
|
rpcUrls,
|
|
1761
|
-
|
|
1963
|
+
chain2.id
|
|
1762
1964
|
);
|
|
1763
|
-
return (0,
|
|
1764
|
-
chain,
|
|
1765
|
-
transport: (0,
|
|
1965
|
+
return (0, import_viem7.createPublicClient)({
|
|
1966
|
+
chain: chain2,
|
|
1967
|
+
transport: (0, import_viem7.http)(url, {
|
|
1766
1968
|
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1767
1969
|
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1768
1970
|
})
|
|
@@ -1776,8 +1978,8 @@ function headerValue(error, name) {
|
|
|
1776
1978
|
return;
|
|
1777
1979
|
if (seen.has(candidate)) return;
|
|
1778
1980
|
seen.add(candidate);
|
|
1779
|
-
const
|
|
1780
|
-
const headers =
|
|
1981
|
+
const record2 = candidate;
|
|
1982
|
+
const headers = record2.headers;
|
|
1781
1983
|
let found;
|
|
1782
1984
|
if (typeof headers?.get === "function") {
|
|
1783
1985
|
found = headers.get(name);
|
|
@@ -1790,7 +1992,7 @@ function headerValue(error, name) {
|
|
|
1790
1992
|
return;
|
|
1791
1993
|
}
|
|
1792
1994
|
for (const key2 of ["cause", "details", "response", "error"])
|
|
1793
|
-
visit(
|
|
1995
|
+
visit(record2[key2], depth + 1);
|
|
1794
1996
|
}
|
|
1795
1997
|
visit(error);
|
|
1796
1998
|
return value;
|
|
@@ -1803,16 +2005,16 @@ function statusCode(error) {
|
|
|
1803
2005
|
return;
|
|
1804
2006
|
if (seen.has(candidate)) return;
|
|
1805
2007
|
seen.add(candidate);
|
|
1806
|
-
const
|
|
2008
|
+
const record2 = candidate;
|
|
1807
2009
|
for (const key2 of ["status", "statusCode"]) {
|
|
1808
|
-
const parsed = Number(
|
|
2010
|
+
const parsed = Number(record2[key2]);
|
|
1809
2011
|
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1810
2012
|
status = parsed;
|
|
1811
2013
|
return;
|
|
1812
2014
|
}
|
|
1813
2015
|
}
|
|
1814
2016
|
for (const key2 of ["cause", "details", "response", "error"])
|
|
1815
|
-
visit(
|
|
2017
|
+
visit(record2[key2], depth + 1);
|
|
1816
2018
|
}
|
|
1817
2019
|
visit(error);
|
|
1818
2020
|
return status;
|
|
@@ -1859,7 +2061,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
|
1859
2061
|
}
|
|
1860
2062
|
|
|
1861
2063
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1862
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI = (0,
|
|
2064
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem8.parseAbi)([
|
|
1863
2065
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1864
2066
|
]);
|
|
1865
2067
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
@@ -1872,6 +2074,17 @@ var SPLITTING_ASSETS = ["USDC", "WETH"];
|
|
|
1872
2074
|
var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
|
|
1873
2075
|
var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1874
2076
|
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
2077
|
+
function belowMinimumCeiling(rejection, token, decimals) {
|
|
2078
|
+
if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
|
|
2079
|
+
const ceiling = (display) => {
|
|
2080
|
+
const places = display.split(".")[1]?.length ?? 0;
|
|
2081
|
+
if (places >= decimals) return void 0;
|
|
2082
|
+
return (0, import_viem8.parseUnits)(display, decimals) + 5n * 10n ** BigInt(decimals - places - 1);
|
|
2083
|
+
};
|
|
2084
|
+
const idle = ceiling(rejection.idle);
|
|
2085
|
+
const minimum = ceiling(rejection.minimum);
|
|
2086
|
+
return idle === void 0 || minimum === void 0 ? void 0 : idle < minimum ? idle : minimum;
|
|
2087
|
+
}
|
|
1875
2088
|
var ZyfaiAgent = class _ZyfaiAgent {
|
|
1876
2089
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1877
2090
|
this.apiKey = apiKey;
|
|
@@ -2026,7 +2239,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2026
2239
|
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
2027
2240
|
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
2028
2241
|
Object.assign(tx, {
|
|
2029
|
-
amount: (0,
|
|
2242
|
+
amount: (0, import_viem8.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
2030
2243
|
amountRaw: evidence.confirmedAmount,
|
|
2031
2244
|
blockNumber: evidence.blockNumber,
|
|
2032
2245
|
logIndexes: evidence.logIndexes,
|
|
@@ -2828,8 +3041,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2828
3041
|
// --- IAgent: Fund operations ---
|
|
2829
3042
|
/** Only after a definitive endpoint rejection, combine a new provider
|
|
2830
3043
|
* snapshot with the Safe's exact idle balance. Never parse its rounded error
|
|
2831
|
-
* amount as proof of zero, or use this to skip sending a new MAX request.
|
|
2832
|
-
|
|
3044
|
+
* amount as proof of zero, or use this to skip sending a new MAX request.
|
|
3045
|
+
* After Zyfai's minimum-withdrawal rejection, exact idle-only dust that fits
|
|
3046
|
+
* within the message's rounded amounts is reported as not withdrawable. */
|
|
3047
|
+
async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner, belowMinimum) {
|
|
2833
3048
|
const internals = this.sdk;
|
|
2834
3049
|
const generation = this.earningsGeneration;
|
|
2835
3050
|
let expired = false;
|
|
@@ -2853,29 +3068,38 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2853
3068
|
this.sdk.getPortfolio(owner).catch(() => void 0)
|
|
2854
3069
|
]);
|
|
2855
3070
|
assertScope();
|
|
2856
|
-
if (!(0,
|
|
3071
|
+
if (!(0, import_viem8.isAddress)(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
|
|
2857
3072
|
throw new OwneyError(
|
|
2858
3073
|
"AGENT_AUTH_FAILED",
|
|
2859
3074
|
"Could not verify Zyfai's withdrawal wallet.",
|
|
2860
3075
|
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2861
3076
|
this.id
|
|
2862
3077
|
);
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
3078
|
+
const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
|
|
3079
|
+
if (!asset || portfolio === void 0) return;
|
|
3080
|
+
const empty = hasEmptyWithdrawalSnapshot(portfolio, chainId, token);
|
|
3081
|
+
const ceiling = belowMinimum && belowMinimumCeiling(belowMinimum, token, asset.decimals);
|
|
3082
|
+
if (!empty && ceiling === void 0) return;
|
|
3083
|
+
const idle = await this.getPublicClient(chainId).readContract({
|
|
3084
|
+
address: asset.address,
|
|
3085
|
+
abi: import_viem8.erc20Abi,
|
|
3086
|
+
functionName: "balanceOf",
|
|
3087
|
+
args: [wallet.address]
|
|
3088
|
+
}).catch(() => void 0);
|
|
3089
|
+
assertScope();
|
|
3090
|
+
if (idle === 0n && empty) throw new OwneyError(
|
|
3091
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3092
|
+
"No funds remain in Zyfai for this asset.",
|
|
3093
|
+
{ available: "0", chainId, token },
|
|
3094
|
+
this.id
|
|
3095
|
+
);
|
|
3096
|
+
if (typeof idle === "bigint" && idle > 0n && ceiling !== void 0 && idle < ceiling && hasIdleOnlyWithdrawalSnapshot(portfolio, chainId, token, asset.decimals, idle))
|
|
3097
|
+
throw new OwneyError(
|
|
2873
3098
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2874
|
-
"
|
|
2875
|
-
{ available: "0", chainId, token },
|
|
3099
|
+
"The remaining Zyfai balance is below Zyfai's minimum withdrawal.",
|
|
3100
|
+
{ available: "0", chainId, token, reason: "ZYFAI_BELOW_MINIMUM_WITHDRAWAL", remainingRaw: idle.toString() },
|
|
2876
3101
|
this.id
|
|
2877
3102
|
);
|
|
2878
|
-
}
|
|
2879
3103
|
};
|
|
2880
3104
|
let timer;
|
|
2881
3105
|
try {
|
|
@@ -2926,7 +3150,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2926
3150
|
throw unavailable();
|
|
2927
3151
|
}
|
|
2928
3152
|
}
|
|
2929
|
-
async withdraw(state, chainId, token, amount, onProgress) {
|
|
3153
|
+
async withdraw(state, chainId, token, amount, onProgress, _gasMode, _onBeforeSubmission, assertOperation) {
|
|
2930
3154
|
if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
|
|
2931
3155
|
throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
|
|
2932
3156
|
const validChainId = isValidChainId(chainId);
|
|
@@ -2962,7 +3186,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2962
3186
|
throw staleSessionError;
|
|
2963
3187
|
}
|
|
2964
3188
|
};
|
|
2965
|
-
|
|
3189
|
+
const assertSubmission = () => {
|
|
3190
|
+
assertCurrent();
|
|
3191
|
+
assertOperation?.();
|
|
3192
|
+
};
|
|
3193
|
+
let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
|
|
2966
3194
|
const authToken = internals.httpClient?.authToken;
|
|
2967
3195
|
let raw2;
|
|
2968
3196
|
try {
|
|
@@ -2981,7 +3209,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2981
3209
|
}
|
|
2982
3210
|
if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
|
|
2983
3211
|
try {
|
|
2984
|
-
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
|
|
3212
|
+
await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner, observation.belowMinimum());
|
|
2985
3213
|
} catch (proofError) {
|
|
2986
3214
|
if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
|
|
2987
3215
|
}
|
|
@@ -2994,9 +3222,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2994
3222
|
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
|
|
2995
3223
|
this.id
|
|
2996
3224
|
);
|
|
3225
|
+
assertSubmission();
|
|
2997
3226
|
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
2998
3227
|
observation.stop();
|
|
2999
|
-
observation = observeWithdrawalSubmission(executionSdk, validChainId, token,
|
|
3228
|
+
observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
|
|
3000
3229
|
}
|
|
3001
3230
|
}
|
|
3002
3231
|
if (!raw2) throw new OwneyError(
|
|
@@ -3111,13 +3340,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
3111
3340
|
this.sdk.getSmartWalletByEOA(owner)
|
|
3112
3341
|
]);
|
|
3113
3342
|
assertScope();
|
|
3114
|
-
if (!(0,
|
|
3343
|
+
if (!(0, import_viem8.isAddress)(wallet.address) || wallet.isDeployed !== false && (wallet.isDeployed !== true || wallet.isOwner !== true) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
|
|
3115
3344
|
const client = this.getPublicClient(validChainId);
|
|
3116
3345
|
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
3117
3346
|
assertScope();
|
|
3118
3347
|
const idle = await client.readContract({
|
|
3119
3348
|
address: asset.address,
|
|
3120
|
-
abi:
|
|
3349
|
+
abi: import_viem8.erc20Abi,
|
|
3121
3350
|
functionName: "balanceOf",
|
|
3122
3351
|
args: [wallet.address],
|
|
3123
3352
|
blockNumber
|
|
@@ -3174,7 +3403,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
3174
3403
|
if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
|
|
3175
3404
|
const idle = await client.readContract({
|
|
3176
3405
|
address: asset.address,
|
|
3177
|
-
abi:
|
|
3406
|
+
abi: import_viem8.erc20Abi,
|
|
3178
3407
|
functionName: "balanceOf",
|
|
3179
3408
|
args: [balance.smartWallet],
|
|
3180
3409
|
blockNumber
|
|
@@ -3379,12 +3608,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
3379
3608
|
};
|
|
3380
3609
|
|
|
3381
3610
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3382
|
-
var
|
|
3611
|
+
var import_viem14 = require("viem");
|
|
3383
3612
|
var import_chains4 = require("viem/chains");
|
|
3384
3613
|
|
|
3385
3614
|
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
3386
3615
|
var import_siwe = require("siwe");
|
|
3387
|
-
var
|
|
3616
|
+
var import_viem9 = require("viem");
|
|
3388
3617
|
var import_chains2 = require("viem/chains");
|
|
3389
3618
|
|
|
3390
3619
|
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
@@ -3499,7 +3728,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
|
3499
3728
|
return new import_siwe.SiweMessage({
|
|
3500
3729
|
scheme: url.protocol.slice(0, -1),
|
|
3501
3730
|
domain: url.host,
|
|
3502
|
-
address: (0,
|
|
3731
|
+
address: (0, import_viem9.getAddress)(address),
|
|
3503
3732
|
uri: url.origin,
|
|
3504
3733
|
version: "1",
|
|
3505
3734
|
chainId,
|
|
@@ -3524,6 +3753,11 @@ var YieldseekerAuth = class {
|
|
|
3524
3753
|
key(state, chainId) {
|
|
3525
3754
|
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
3526
3755
|
}
|
|
3756
|
+
/** Observation must never open a sign-in prompt. */
|
|
3757
|
+
peekToken(state, chainId) {
|
|
3758
|
+
const token = this.tokens.get(this.key(state, chainId)) ?? readYieldseekerSession(state.walletAddress, chainId);
|
|
3759
|
+
return token && this.isReusable(token, state, chainId) ? token : void 0;
|
|
3760
|
+
}
|
|
3527
3761
|
async getToken(state, chainId) {
|
|
3528
3762
|
const key2 = this.key(state, chainId);
|
|
3529
3763
|
const scope = { address: state.walletAddress, chainId };
|
|
@@ -3594,15 +3828,15 @@ var YieldseekerAuth = class {
|
|
|
3594
3828
|
clearYieldseekerSession(state.walletAddress, chainId);
|
|
3595
3829
|
}
|
|
3596
3830
|
async sign(state, chainId) {
|
|
3597
|
-
const account = (0,
|
|
3598
|
-
const publicClient = (0,
|
|
3831
|
+
const account = (0, import_viem9.getAddress)(state.walletAddress);
|
|
3832
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
3599
3833
|
chain: import_chains2.base,
|
|
3600
|
-
transport: (0,
|
|
3834
|
+
transport: (0, import_viem9.custom)(state.provider)
|
|
3601
3835
|
});
|
|
3602
|
-
const walletClient = (0,
|
|
3836
|
+
const walletClient = (0, import_viem9.createWalletClient)({
|
|
3603
3837
|
account,
|
|
3604
3838
|
chain: import_chains2.base,
|
|
3605
|
-
transport: (0,
|
|
3839
|
+
transport: (0, import_viem9.custom)(state.provider)
|
|
3606
3840
|
});
|
|
3607
3841
|
await ensureWalletOnChain(
|
|
3608
3842
|
publicClient,
|
|
@@ -3708,10 +3942,10 @@ var YieldseekerApiError = class extends Error {
|
|
|
3708
3942
|
};
|
|
3709
3943
|
function providerError(body, fallback2) {
|
|
3710
3944
|
if (!body || typeof body !== "object") return { code: fallback2 };
|
|
3711
|
-
const
|
|
3945
|
+
const record2 = body;
|
|
3712
3946
|
return {
|
|
3713
|
-
code: typeof
|
|
3714
|
-
fields:
|
|
3947
|
+
code: typeof record2.message === "string" ? record2.message : fallback2,
|
|
3948
|
+
fields: record2.fields && typeof record2.fields === "object" ? record2.fields : void 0
|
|
3715
3949
|
};
|
|
3716
3950
|
}
|
|
3717
3951
|
var YieldseekerApiClient = class {
|
|
@@ -3769,7 +4003,7 @@ var YieldseekerApiClient = class {
|
|
|
3769
4003
|
};
|
|
3770
4004
|
|
|
3771
4005
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
3772
|
-
var
|
|
4006
|
+
var import_viem10 = require("viem");
|
|
3773
4007
|
|
|
3774
4008
|
// src/lib/helpers/snapshot-apy.ts
|
|
3775
4009
|
var DAY_MS = 864e5;
|
|
@@ -3836,7 +4070,7 @@ function raw(value, endpoint) {
|
|
|
3836
4070
|
return BigInt(value);
|
|
3837
4071
|
}
|
|
3838
4072
|
function decimal(value, decimals, endpoint) {
|
|
3839
|
-
return (0,
|
|
4073
|
+
return (0, import_viem10.formatUnits)(raw(value, endpoint), decimals);
|
|
3840
4074
|
}
|
|
3841
4075
|
function balanceRaw(value, endpoint) {
|
|
3842
4076
|
const amount = raw(value, endpoint);
|
|
@@ -3844,10 +4078,10 @@ function balanceRaw(value, endpoint) {
|
|
|
3844
4078
|
return amount;
|
|
3845
4079
|
}
|
|
3846
4080
|
function balanceDecimal(value, decimals, endpoint) {
|
|
3847
|
-
return (0,
|
|
4081
|
+
return (0, import_viem10.formatUnits)(balanceRaw(value, endpoint), decimals);
|
|
3848
4082
|
}
|
|
3849
4083
|
function usd(rawAmount2, decimals, price) {
|
|
3850
|
-
return Number((0,
|
|
4084
|
+
return Number((0, import_viem10.formatUnits)(rawAmount2, decimals)) * price;
|
|
3851
4085
|
}
|
|
3852
4086
|
function percent(value) {
|
|
3853
4087
|
const result = Number(value);
|
|
@@ -3865,16 +4099,16 @@ function riskAdjustedApyForDays(option, days) {
|
|
|
3865
4099
|
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
3866
4100
|
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
3867
4101
|
}
|
|
3868
|
-
function assetAddressValue(
|
|
3869
|
-
const entry = Object.entries(
|
|
4102
|
+
function assetAddressValue(record2, address) {
|
|
4103
|
+
const entry = Object.entries(record2).find(
|
|
3870
4104
|
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
3871
4105
|
);
|
|
3872
4106
|
return entry?.[1] ?? "0";
|
|
3873
4107
|
}
|
|
3874
4108
|
function position(value, asset, baseAssetDecimals) {
|
|
3875
|
-
const option = value?.
|
|
3876
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0,
|
|
3877
|
-
return invalid("
|
|
4109
|
+
const option = value?.vaultOption;
|
|
4110
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem10.isAddress)(option.address)) {
|
|
4111
|
+
return invalid("vault positions", "missing vault metadata");
|
|
3878
4112
|
}
|
|
3879
4113
|
return {
|
|
3880
4114
|
chain: "BASE",
|
|
@@ -3890,10 +4124,10 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
3890
4124
|
amount: balanceDecimal(
|
|
3891
4125
|
value.assetsBase,
|
|
3892
4126
|
baseAssetDecimals,
|
|
3893
|
-
"
|
|
4127
|
+
"vault positions"
|
|
3894
4128
|
),
|
|
3895
|
-
amountRaw: balanceRaw(value.assetsRaw, "
|
|
3896
|
-
withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "
|
|
4129
|
+
amountRaw: balanceRaw(value.assetsRaw, "vault positions").toString(),
|
|
4130
|
+
withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "vault positions").toString(),
|
|
3897
4131
|
apy: percent(option.riskAdjustedApy),
|
|
3898
4132
|
tvl: Number(option.totalDepositsUsd),
|
|
3899
4133
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -3966,7 +4200,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
3966
4200
|
chain: "BASE",
|
|
3967
4201
|
chainId: 8453,
|
|
3968
4202
|
asset: context.asset,
|
|
3969
|
-
amount: (0,
|
|
4203
|
+
amount: (0, import_viem10.formatUnits)(amount, context.snapshot.baseAssetDecimals)
|
|
3970
4204
|
});
|
|
3971
4205
|
lifetimeEarnings += usd(
|
|
3972
4206
|
amount,
|
|
@@ -4112,52 +4346,82 @@ function actionEntry(action) {
|
|
|
4112
4346
|
rebalanceLog: []
|
|
4113
4347
|
};
|
|
4114
4348
|
}
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
const
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
if (
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
|
|
4127
|
-
if (typeof leg.vaultAddress !== "string" || !(0, import_viem9.isAddress)(leg.vaultAddress)) return void 0;
|
|
4349
|
+
var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
|
|
4350
|
+
function allocationLegs(value, context, requireAmount) {
|
|
4351
|
+
if (!Array.isArray(value)) return void 0;
|
|
4352
|
+
const legs = [];
|
|
4353
|
+
for (const item of value) {
|
|
4354
|
+
if (!item || typeof item !== "object") return void 0;
|
|
4355
|
+
const leg = item;
|
|
4356
|
+
const symbol2 = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
|
|
4357
|
+
if ((requireAmount || symbol2) && symbol2 !== context.asset && !(context.asset === "WETH" && symbol2 === "ETH"))
|
|
4358
|
+
return void 0;
|
|
4359
|
+
if (typeof leg.vaultAddress !== "string" || !(0, import_viem10.isAddress)(leg.vaultAddress)) return void 0;
|
|
4128
4360
|
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;
|
|
4129
|
-
if (rawAmount2 === void 0 || rawAmount2 <= 0n) return void 0;
|
|
4130
|
-
const
|
|
4131
|
-
positions.
|
|
4361
|
+
if (requireAmount && (rawAmount2 === void 0 || rawAmount2 <= 0n)) return void 0;
|
|
4362
|
+
const vault = leg.vaultAddress.toLowerCase();
|
|
4363
|
+
const option = context.positions.find((position2) => position2.vaultOption.address.toLowerCase() === vault)?.vaultOption;
|
|
4364
|
+
const receipt = context.historic?.assets?.find((asset) => asset.address.toLowerCase() === vault && !asset.isSpam);
|
|
4365
|
+
legs.push({
|
|
4132
4366
|
protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
|
|
4133
|
-
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
|
|
4134
|
-
|
|
4135
|
-
amount: (0, import_viem9.formatUnits)(rawAmount2, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
|
|
4367
|
+
pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name ?? (receipt?.name || receipt?.symbol || void 0),
|
|
4368
|
+
rawAmount: rawAmount2
|
|
4136
4369
|
});
|
|
4137
|
-
amountRaw += rawAmount2;
|
|
4138
4370
|
}
|
|
4371
|
+
return legs;
|
|
4372
|
+
}
|
|
4373
|
+
function allocationEntry(action, context) {
|
|
4374
|
+
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
4375
|
+
const hash2 = action.details?.depositTransactionHash;
|
|
4376
|
+
const withdrawHash = action.details?.withdrawTransactionHash;
|
|
4377
|
+
if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
|
|
4378
|
+
const deposits = allocationLegs(action.details?.deposits, context, true);
|
|
4379
|
+
const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
|
|
4380
|
+
if (!deposits?.length || !withdrawals) return null;
|
|
4381
|
+
const decimals = YIELDSEEKER_ASSET_METADATA[context.asset].decimals;
|
|
4382
|
+
const positions = deposits.map((leg) => ({
|
|
4383
|
+
protocol: leg.protocol,
|
|
4384
|
+
pool: leg.pool,
|
|
4385
|
+
tokenSymbol: context.asset,
|
|
4386
|
+
amount: (0, import_viem10.formatUnits)(leg.rawAmount, decimals)
|
|
4387
|
+
}));
|
|
4388
|
+
const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
|
|
4389
|
+
const rebalance = withdrawals.length > 0;
|
|
4390
|
+
const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
|
|
4391
|
+
const source = withdrawals.length === 1 ? withdrawals[0] : {
|
|
4392
|
+
protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
|
|
4393
|
+
pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
|
|
4394
|
+
};
|
|
4139
4395
|
return {
|
|
4140
4396
|
agent: "yieldseeker",
|
|
4141
|
-
action: "Deposit",
|
|
4397
|
+
action: rebalance ? "Rebalance" : "Deposit",
|
|
4142
4398
|
date: action.createdDate,
|
|
4143
4399
|
oldApy: null,
|
|
4144
4400
|
newApy: null,
|
|
4145
4401
|
positions,
|
|
4146
|
-
rebalanceLog:
|
|
4402
|
+
rebalanceLog: rebalance ? deposits.map((leg) => ({
|
|
4403
|
+
fromProtocol: source.protocol,
|
|
4404
|
+
...source.pool ? { fromPool: source.pool } : {},
|
|
4405
|
+
toProtocol: leg.protocol,
|
|
4406
|
+
...leg.pool ? { toPool: leg.pool } : {},
|
|
4407
|
+
tokenSymbol: context.asset,
|
|
4408
|
+
amount: (0, import_viem10.formatUnits)(leg.rawAmount, decimals),
|
|
4409
|
+
status: "success"
|
|
4410
|
+
})) : [],
|
|
4147
4411
|
transactions: [{
|
|
4148
|
-
txHashes:
|
|
4412
|
+
txHashes: hashes,
|
|
4149
4413
|
chainId: context.agent.chainId,
|
|
4150
4414
|
tokenAddress: context.agent.assetAddress,
|
|
4151
4415
|
tokenSymbol: context.asset,
|
|
4152
4416
|
agentWallet: context.wallet.walletAddress,
|
|
4153
4417
|
amountRaw: String(amountRaw),
|
|
4154
|
-
amount: (0,
|
|
4418
|
+
amount: (0, import_viem10.formatUnits)(amountRaw, decimals)
|
|
4155
4419
|
}]
|
|
4156
4420
|
};
|
|
4157
4421
|
}
|
|
4158
4422
|
function depositDestination(context, movement) {
|
|
4159
4423
|
const to = movement.toAddress.toLowerCase();
|
|
4160
|
-
const option = context.positions.find(({
|
|
4424
|
+
const option = context.positions.find(({ vaultOption }) => vaultOption.chainId === movement.chainId && vaultOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && vaultOption.address.toLowerCase() === to)?.vaultOption;
|
|
4161
4425
|
if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
|
|
4162
4426
|
const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
|
|
4163
4427
|
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);
|
|
@@ -4222,6 +4486,23 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
4222
4486
|
seenMovements.add(key2);
|
|
4223
4487
|
return true;
|
|
4224
4488
|
});
|
|
4489
|
+
const allocations = /* @__PURE__ */ new Map();
|
|
4490
|
+
const rebalanceHashes = /* @__PURE__ */ new Set();
|
|
4491
|
+
const actions = [];
|
|
4492
|
+
for (const action of context.actions ?? []) {
|
|
4493
|
+
const allocation = allocationEntry(action, context);
|
|
4494
|
+
if (allocation === null) continue;
|
|
4495
|
+
if (!allocation) {
|
|
4496
|
+
const entry = actionEntry(action);
|
|
4497
|
+
if (entry) actions.push(entry);
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4500
|
+
const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
4501
|
+
if (allocations.has(hash2)) continue;
|
|
4502
|
+
allocations.set(hash2, allocation);
|
|
4503
|
+
if (allocation.action === "Rebalance")
|
|
4504
|
+
for (const value of allocation.transactions[0].txHashes) rebalanceHashes.add(value.toLowerCase());
|
|
4505
|
+
}
|
|
4225
4506
|
const movementEntries = movements.map(
|
|
4226
4507
|
(movement) => movementEntry(
|
|
4227
4508
|
movement,
|
|
@@ -4232,19 +4513,10 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
4232
4513
|
options.vaultAddresses,
|
|
4233
4514
|
depositDestination(context, movement)
|
|
4234
4515
|
)
|
|
4235
|
-
).filter((entry) => entry !== void 0);
|
|
4516
|
+
).filter((entry) => entry !== void 0 && !((entry.action === "Deposit" || entry.action === "Top up") && rebalanceHashes.has(entry.transactions[0]?.txHashes[0]?.toLowerCase() ?? "")));
|
|
4236
4517
|
const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
if (allocation) {
|
|
4240
|
-
const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
|
|
4241
|
-
if (deposits.has(hash2)) return [];
|
|
4242
|
-
deposits.add(hash2);
|
|
4243
|
-
return [allocation];
|
|
4244
|
-
}
|
|
4245
|
-
const entry = actionEntry(action);
|
|
4246
|
-
return entry ? [entry] : [];
|
|
4247
|
-
});
|
|
4518
|
+
for (const [hash2, allocation] of allocations)
|
|
4519
|
+
if (allocation.action === "Rebalance" || !deposits.has(hash2)) actions.push(allocation);
|
|
4248
4520
|
return [...movementEntries, ...actions];
|
|
4249
4521
|
});
|
|
4250
4522
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -4275,7 +4547,7 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
4275
4547
|
const known = new Set(target.logIndexes ?? []);
|
|
4276
4548
|
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
4277
4549
|
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
4278
|
-
target.amount = (0,
|
|
4550
|
+
target.amount = (0, import_viem10.formatUnits)(
|
|
4279
4551
|
BigInt(target.amountRaw),
|
|
4280
4552
|
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
4281
4553
|
);
|
|
@@ -4322,8 +4594,8 @@ function mapYieldseekerProfile(address, contexts) {
|
|
|
4322
4594
|
const protocols = /* @__PURE__ */ new Set();
|
|
4323
4595
|
for (const context of contexts) {
|
|
4324
4596
|
for (const current of context.positions) {
|
|
4325
|
-
if (current.
|
|
4326
|
-
protocols.add(String(current.
|
|
4597
|
+
if (current.vaultOption?.provider) {
|
|
4598
|
+
protocols.add(String(current.vaultOption.provider));
|
|
4327
4599
|
}
|
|
4328
4600
|
}
|
|
4329
4601
|
}
|
|
@@ -4341,7 +4613,7 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
4341
4613
|
const perAsset = {};
|
|
4342
4614
|
const all = [];
|
|
4343
4615
|
for (const entry of options) {
|
|
4344
|
-
const apys = entry.
|
|
4616
|
+
const apys = entry.vaultOptions.map(
|
|
4345
4617
|
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
4346
4618
|
).filter(Number.isFinite);
|
|
4347
4619
|
if (apys.length === 0) continue;
|
|
@@ -4356,7 +4628,7 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
4356
4628
|
}
|
|
4357
4629
|
|
|
4358
4630
|
// src/lib/remaining-vault-balance.ts
|
|
4359
|
-
var
|
|
4631
|
+
var import_viem11 = require("viem");
|
|
4360
4632
|
async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber) {
|
|
4361
4633
|
const addresses = [...new Set(vaults.map((value) => value.toLowerCase()))];
|
|
4362
4634
|
if (addresses.length > 64) return void 0;
|
|
@@ -4367,8 +4639,8 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
|
|
|
4367
4639
|
blockNumber,
|
|
4368
4640
|
allowFailure: true,
|
|
4369
4641
|
contracts: batch.map((address) => ({
|
|
4370
|
-
address: (0,
|
|
4371
|
-
abi:
|
|
4642
|
+
address: (0, import_viem11.getAddress)(address),
|
|
4643
|
+
abi: import_viem11.erc20Abi,
|
|
4372
4644
|
functionName: "balanceOf",
|
|
4373
4645
|
args: [wallet]
|
|
4374
4646
|
}))
|
|
@@ -4378,15 +4650,15 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
|
|
|
4378
4650
|
for (let index = 0; index < batch.length; index++) {
|
|
4379
4651
|
const result = balances[index];
|
|
4380
4652
|
if (result?.status !== "success" || typeof result.result !== "bigint" || result.result < 0n) return void 0;
|
|
4381
|
-
if (result.result > 0n) funded.push({ address: (0,
|
|
4653
|
+
if (result.result > 0n) funded.push({ address: (0, import_viem11.getAddress)(batch[index]), shares: result.result });
|
|
4382
4654
|
}
|
|
4383
4655
|
if (!funded.length) return 0n;
|
|
4384
4656
|
const valuations = await client.multicall({
|
|
4385
4657
|
blockNumber,
|
|
4386
4658
|
allowFailure: true,
|
|
4387
4659
|
contracts: funded.flatMap(({ address, shares }) => [
|
|
4388
|
-
{ address, abi:
|
|
4389
|
-
{ address, abi:
|
|
4660
|
+
{ address, abi: import_viem11.erc4626Abi, functionName: "asset" },
|
|
4661
|
+
{ address, abi: import_viem11.erc4626Abi, functionName: "convertToAssets", args: [shares] }
|
|
4390
4662
|
])
|
|
4391
4663
|
});
|
|
4392
4664
|
if (valuations.length !== funded.length * 2) return void 0;
|
|
@@ -4402,12 +4674,12 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
|
|
|
4402
4674
|
}
|
|
4403
4675
|
|
|
4404
4676
|
// src/agents/yieldseeker/yieldseeker.wallet-version.ts
|
|
4405
|
-
var
|
|
4677
|
+
var import_viem12 = require("viem");
|
|
4406
4678
|
var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
|
|
4407
4679
|
var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
|
|
4408
4680
|
var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
|
|
4409
4681
|
var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
4410
|
-
var walletAbi = (0,
|
|
4682
|
+
var walletAbi = (0, import_viem12.parseAbi)([
|
|
4411
4683
|
"function owner() view returns (address)",
|
|
4412
4684
|
"function ownerAgentIndex() view returns (uint256)",
|
|
4413
4685
|
"function baseAsset() view returns (address)",
|
|
@@ -4415,7 +4687,7 @@ var walletAbi = (0, import_viem11.parseAbi)([
|
|
|
4415
4687
|
"function upgradeToLatest()",
|
|
4416
4688
|
"event Upgraded(address indexed implementation)"
|
|
4417
4689
|
]);
|
|
4418
|
-
var factoryAbi = (0,
|
|
4690
|
+
var factoryAbi = (0, import_viem12.parseAbi)([
|
|
4419
4691
|
"function userWallets(address owner, uint256 index) view returns (address)",
|
|
4420
4692
|
"function agentWalletImplementation() view returns (address)"
|
|
4421
4693
|
]);
|
|
@@ -4423,9 +4695,9 @@ function yieldseekerOwnerPath(code) {
|
|
|
4423
4695
|
return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
|
|
4424
4696
|
}
|
|
4425
4697
|
async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
|
|
4426
|
-
const agentWallet = (0,
|
|
4427
|
-
const expectedOwner = (0,
|
|
4428
|
-
const expectedAsset = (0,
|
|
4698
|
+
const agentWallet = (0, import_viem12.getAddress)(wallet);
|
|
4699
|
+
const expectedOwner = (0, import_viem12.getAddress)(owner);
|
|
4700
|
+
const expectedAsset = (0, import_viem12.getAddress)(asset);
|
|
4429
4701
|
const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
|
|
4430
4702
|
client.getBytecode({ address: agentWallet, blockNumber }),
|
|
4431
4703
|
client.getBytecode({ address: expectedOwner, blockNumber }),
|
|
@@ -4439,7 +4711,7 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
|
|
|
4439
4711
|
if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
|
|
4440
4712
|
throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
|
|
4441
4713
|
}
|
|
4442
|
-
const implementation = (0,
|
|
4714
|
+
const implementation = (0, import_viem12.getAddress)(`0x${slot.slice(-40)}`);
|
|
4443
4715
|
const mapped = await client.readContract({
|
|
4444
4716
|
address: YIELDSEEKER_FACTORY,
|
|
4445
4717
|
abi: factoryAbi,
|
|
@@ -4447,8 +4719,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
|
|
|
4447
4719
|
args: [expectedOwner, index],
|
|
4448
4720
|
blockNumber
|
|
4449
4721
|
});
|
|
4450
|
-
const verified = (0,
|
|
4451
|
-
const version = !verified ? "unknown" : (0,
|
|
4722
|
+
const verified = (0, import_viem12.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem12.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem12.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem12.isAddressEqual)(mapped, agentWallet);
|
|
4723
|
+
const version = !verified ? "unknown" : (0, import_viem12.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem12.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
|
|
4452
4724
|
return {
|
|
4453
4725
|
agentWallet,
|
|
4454
4726
|
owner: actualOwner,
|
|
@@ -4457,13 +4729,13 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
|
|
|
4457
4729
|
implementation,
|
|
4458
4730
|
version,
|
|
4459
4731
|
verified,
|
|
4460
|
-
upgradeAvailable: version === "v1" && (0,
|
|
4732
|
+
upgradeAvailable: version === "v1" && (0, import_viem12.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
|
|
4461
4733
|
};
|
|
4462
4734
|
}
|
|
4463
4735
|
var YIELDSEEKER_UPGRADE_ABI = walletAbi;
|
|
4464
4736
|
|
|
4465
4737
|
// src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
|
|
4466
|
-
var
|
|
4738
|
+
var import_viem13 = require("viem");
|
|
4467
4739
|
var import_account_abstraction2 = require("viem/account-abstraction");
|
|
4468
4740
|
var import_accounts = require("viem/accounts");
|
|
4469
4741
|
var import_chains3 = require("viem/chains");
|
|
@@ -4550,7 +4822,7 @@ function rpcDiagnostics(data) {
|
|
|
4550
4822
|
details.revertName = data.revertName;
|
|
4551
4823
|
if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
|
|
4552
4824
|
if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
|
|
4553
|
-
const panicCode = (0,
|
|
4825
|
+
const panicCode = (0, import_viem13.toHex)(BigInt(data.panicCode));
|
|
4554
4826
|
if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
|
|
4555
4827
|
details.panicCode = panicCode;
|
|
4556
4828
|
}
|
|
@@ -4560,11 +4832,11 @@ function isHash(value) {
|
|
|
4560
4832
|
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
4561
4833
|
}
|
|
4562
4834
|
async function assertOwnerSession(state, owner) {
|
|
4563
|
-
const [
|
|
4835
|
+
const [chain2, accounts] = await Promise.all([
|
|
4564
4836
|
state.provider.request({ method: "eth_chainId" }),
|
|
4565
4837
|
state.provider.request({ method: "eth_accounts" })
|
|
4566
4838
|
]);
|
|
4567
|
-
if (Number(
|
|
4839
|
+
if (Number(chain2) !== 8453)
|
|
4568
4840
|
throw new OwneyError(
|
|
4569
4841
|
"CHAIN_MISMATCH",
|
|
4570
4842
|
"Switch back to Base before withdrawing.",
|
|
@@ -4770,14 +5042,14 @@ async function submitEoa(input) {
|
|
|
4770
5042
|
throw sponsorError("Base did not return EIP-1559 gas fees.", true);
|
|
4771
5043
|
let operation = {
|
|
4772
5044
|
sender: input.agentWallet,
|
|
4773
|
-
nonce: (0,
|
|
5045
|
+
nonce: (0, import_viem13.toHex)(nonce),
|
|
4774
5046
|
initCode: "0x",
|
|
4775
5047
|
callData: input.callData,
|
|
4776
|
-
callGasLimit: (0,
|
|
4777
|
-
verificationGasLimit: (0,
|
|
4778
|
-
preVerificationGas: (0,
|
|
4779
|
-
maxFeePerGas: (0,
|
|
4780
|
-
maxPriorityFeePerGas: (0,
|
|
5048
|
+
callGasLimit: (0, import_viem13.toHex)(3e5),
|
|
5049
|
+
verificationGasLimit: (0, import_viem13.toHex)(3e5),
|
|
5050
|
+
preVerificationGas: (0, import_viem13.toHex)(6e4),
|
|
5051
|
+
maxFeePerGas: (0, import_viem13.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
|
|
5052
|
+
maxPriorityFeePerGas: (0, import_viem13.toHex)(fees.maxPriorityFeePerGas),
|
|
4781
5053
|
paymasterAndData: "0x",
|
|
4782
5054
|
signature: "0x"
|
|
4783
5055
|
};
|
|
@@ -4785,7 +5057,7 @@ async function submitEoa(input) {
|
|
|
4785
5057
|
const stub = await rpc(
|
|
4786
5058
|
input.rpcUrl,
|
|
4787
5059
|
"pm_getPaymasterStubData",
|
|
4788
|
-
[operation, ENTRY_POINT_V06, (0,
|
|
5060
|
+
[operation, ENTRY_POINT_V06, (0, import_viem13.toHex)(8453), {}],
|
|
4789
5061
|
"prepare"
|
|
4790
5062
|
);
|
|
4791
5063
|
if (!stub.paymasterAndData?.startsWith("0x"))
|
|
@@ -4800,7 +5072,7 @@ async function submitEoa(input) {
|
|
|
4800
5072
|
const final = await rpc(
|
|
4801
5073
|
input.rpcUrl,
|
|
4802
5074
|
"pm_getPaymasterData",
|
|
4803
|
-
[operation, ENTRY_POINT_V06, (0,
|
|
5075
|
+
[operation, ENTRY_POINT_V06, (0, import_viem13.toHex)(8453), {}],
|
|
4804
5076
|
"prepare"
|
|
4805
5077
|
);
|
|
4806
5078
|
if (!final.paymasterAndData?.startsWith("0x"))
|
|
@@ -4810,11 +5082,12 @@ async function submitEoa(input) {
|
|
|
4810
5082
|
const userOpHash = operationHash(operation);
|
|
4811
5083
|
await assertOwnerSession(input.state, input.owner);
|
|
4812
5084
|
assertIntentFresh(input.expiresAt);
|
|
4813
|
-
const wallet = (0,
|
|
5085
|
+
const wallet = (0, import_viem13.createWalletClient)({
|
|
4814
5086
|
account: input.owner,
|
|
4815
5087
|
chain: import_chains3.base,
|
|
4816
|
-
transport: (0,
|
|
5088
|
+
transport: (0, import_viem13.custom)(input.state.provider)
|
|
4817
5089
|
});
|
|
5090
|
+
input.assertCurrent?.();
|
|
4818
5091
|
const signature = await wallet.signMessage({ message: { raw: userOpHash } });
|
|
4819
5092
|
operation = { ...operation, signature };
|
|
4820
5093
|
await assertOwnerSession(input.state, input.owner);
|
|
@@ -4832,7 +5105,9 @@ async function submitEoa(input) {
|
|
|
4832
5105
|
paymasterAndData: operation.paymasterAndData
|
|
4833
5106
|
};
|
|
4834
5107
|
savePending(input.onPending, { userOpHash, userOperation }, true);
|
|
5108
|
+
input.assertCurrent?.();
|
|
4835
5109
|
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
5110
|
+
input.assertCurrent?.();
|
|
4836
5111
|
const submittedHash = await rpc(
|
|
4837
5112
|
input.rpcUrl,
|
|
4838
5113
|
"eth_sendUserOperation",
|
|
@@ -4891,22 +5166,24 @@ async function submitSmart(input) {
|
|
|
4891
5166
|
} catch {
|
|
4892
5167
|
throw sponsorError("This account cannot check paymaster support on Base.", true);
|
|
4893
5168
|
}
|
|
4894
|
-
const supportsPaymaster = capabilities?.[(0,
|
|
5169
|
+
const supportsPaymaster = capabilities?.[(0, import_viem13.toHex)(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
|
|
4895
5170
|
if (supportsPaymaster !== true)
|
|
4896
5171
|
throw sponsorError("This account cannot use a paymaster on Base.", true);
|
|
4897
5172
|
await assertOwnerSession(input.state, input.owner);
|
|
4898
5173
|
let sent;
|
|
4899
5174
|
assertIntentFresh(input.expiresAt);
|
|
4900
|
-
const requestedId = (0,
|
|
5175
|
+
const requestedId = (0, import_viem13.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
4901
5176
|
savePending(input.onPending, { callsId: requestedId }, true);
|
|
5177
|
+
input.assertCurrent?.();
|
|
4902
5178
|
await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
|
|
5179
|
+
input.assertCurrent?.();
|
|
4903
5180
|
try {
|
|
4904
5181
|
sent = await input.state.provider.request({
|
|
4905
5182
|
method: "wallet_sendCalls",
|
|
4906
5183
|
params: [{
|
|
4907
5184
|
version: "2.0.0",
|
|
4908
5185
|
from: input.owner,
|
|
4909
|
-
chainId: (0,
|
|
5186
|
+
chainId: (0, import_viem13.toHex)(8453),
|
|
4910
5187
|
id: requestedId,
|
|
4911
5188
|
atomicRequired: false,
|
|
4912
5189
|
calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
|
|
@@ -4981,7 +5258,7 @@ async function submitSmart(input) {
|
|
|
4981
5258
|
);
|
|
4982
5259
|
}
|
|
4983
5260
|
async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
4984
|
-
const owner = (0,
|
|
5261
|
+
const owner = (0, import_viem13.getAddress)(input.state.walletAddress);
|
|
4985
5262
|
const { intent, rpcUrl } = await requestIntent({
|
|
4986
5263
|
apiKey: input.apiKey,
|
|
4987
5264
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -5000,7 +5277,7 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
|
|
|
5000
5277
|
return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
|
|
5001
5278
|
}
|
|
5002
5279
|
async function submitYieldseekerSponsoredUpgrade(input) {
|
|
5003
|
-
const owner = (0,
|
|
5280
|
+
const owner = (0, import_viem13.getAddress)(input.state.walletAddress);
|
|
5004
5281
|
const { intent, rpcUrl } = await requestIntent({
|
|
5005
5282
|
apiKey: input.apiKey,
|
|
5006
5283
|
routingApiBaseUrl: input.routingApiBaseUrl,
|
|
@@ -5026,7 +5303,7 @@ var OWNEY_AGENT_RULE_PRESET = "explorative";
|
|
|
5026
5303
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
5027
5304
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
5028
5305
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
5029
|
-
var
|
|
5306
|
+
var YIELDSEEKER_VAULT_OPTIONS_CACHE_MS = 6e4;
|
|
5030
5307
|
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
5031
5308
|
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
5032
5309
|
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
@@ -5129,6 +5406,7 @@ var YieldseekerAgent = class {
|
|
|
5129
5406
|
pendingReads = /* @__PURE__ */ new Map();
|
|
5130
5407
|
readGeneration = 0;
|
|
5131
5408
|
portfolioVersions = /* @__PURE__ */ new Map();
|
|
5409
|
+
liquidityVersions = /* @__PURE__ */ new Map();
|
|
5132
5410
|
snapshotFailures = /* @__PURE__ */ new Map();
|
|
5133
5411
|
standardReadFailures = /* @__PURE__ */ new Map();
|
|
5134
5412
|
reconcileUntil = /* @__PURE__ */ new Map();
|
|
@@ -5138,8 +5416,8 @@ var YieldseekerAgent = class {
|
|
|
5138
5416
|
// settlement; keep slow reconciliation after the fast window expires.
|
|
5139
5417
|
snapshotMovements = /* @__PURE__ */ new Map();
|
|
5140
5418
|
movementEpochs = /* @__PURE__ */ new Map();
|
|
5141
|
-
|
|
5142
|
-
|
|
5419
|
+
vaultOptions = /* @__PURE__ */ new Map();
|
|
5420
|
+
pendingVaultOptions = /* @__PURE__ */ new Map();
|
|
5143
5421
|
constructor(owneyApiKey, options = {}) {
|
|
5144
5422
|
this.owneyApiKey = owneyApiKey;
|
|
5145
5423
|
this.routingApiBaseUrl = options.routingApiBaseUrl;
|
|
@@ -5168,8 +5446,8 @@ var YieldseekerAgent = class {
|
|
|
5168
5446
|
if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
|
|
5169
5447
|
const status = await readYieldseekerWalletStatus(
|
|
5170
5448
|
this.getReceiptClient(),
|
|
5171
|
-
(0,
|
|
5172
|
-
(0,
|
|
5449
|
+
(0, import_viem14.getAddress)(context.wallet.walletAddress),
|
|
5450
|
+
(0, import_viem14.getAddress)(state.walletAddress),
|
|
5173
5451
|
YIELDSEEKER_ASSET_METADATA[asset].address
|
|
5174
5452
|
);
|
|
5175
5453
|
this.rememberWalletStatus(key2, status);
|
|
@@ -5199,12 +5477,12 @@ var YieldseekerAgent = class {
|
|
|
5199
5477
|
const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
|
|
5200
5478
|
const confirmUpgrade = async (hash2, receipt2) => {
|
|
5201
5479
|
this.withdrawalVersionCache.delete(versionKey);
|
|
5202
|
-
const upgradeTopic = (0,
|
|
5480
|
+
const upgradeTopic = (0, import_viem14.encodeEventTopics)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
|
|
5203
5481
|
const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
|
|
5204
5482
|
const expectedUpgrade = (() => {
|
|
5205
5483
|
if (!lastUpgrade || lastUpgrade.removed) return false;
|
|
5206
5484
|
try {
|
|
5207
|
-
const event = (0,
|
|
5485
|
+
const event = (0, import_viem14.decodeEventLog)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
|
|
5208
5486
|
return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
|
|
5209
5487
|
} catch {
|
|
5210
5488
|
return false;
|
|
@@ -5342,7 +5620,7 @@ var YieldseekerAgent = class {
|
|
|
5342
5620
|
{ version: before.version },
|
|
5343
5621
|
this.id
|
|
5344
5622
|
);
|
|
5345
|
-
const data = (0,
|
|
5623
|
+
const data = (0, import_viem14.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
|
|
5346
5624
|
let txHash;
|
|
5347
5625
|
let receipt;
|
|
5348
5626
|
if (gasMode === "sponsored") {
|
|
@@ -5385,7 +5663,7 @@ var YieldseekerAgent = class {
|
|
|
5385
5663
|
let submittedHash;
|
|
5386
5664
|
try {
|
|
5387
5665
|
txHash = await this.submitTransaction(state, chainId, {
|
|
5388
|
-
from: (0,
|
|
5666
|
+
from: (0, import_viem14.getAddress)(state.walletAddress),
|
|
5389
5667
|
to: before.agentWallet,
|
|
5390
5668
|
data,
|
|
5391
5669
|
value: "0",
|
|
@@ -5433,6 +5711,7 @@ var YieldseekerAgent = class {
|
|
|
5433
5711
|
this.readCache.clear();
|
|
5434
5712
|
this.pendingReads.clear();
|
|
5435
5713
|
this.portfolioVersions.clear();
|
|
5714
|
+
this.liquidityVersions.clear();
|
|
5436
5715
|
this.snapshotFailures.clear();
|
|
5437
5716
|
this.standardReadFailures.clear();
|
|
5438
5717
|
this.reconcileUntil.clear();
|
|
@@ -5492,12 +5771,12 @@ var YieldseekerAgent = class {
|
|
|
5492
5771
|
} else {
|
|
5493
5772
|
try {
|
|
5494
5773
|
txHash = await this.submitTransaction(state, chainId, {
|
|
5495
|
-
from: (0,
|
|
5774
|
+
from: (0, import_viem14.getAddress)(state.walletAddress),
|
|
5496
5775
|
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
5497
|
-
data: (0,
|
|
5498
|
-
abi:
|
|
5776
|
+
data: (0, import_viem14.encodeFunctionData)({
|
|
5777
|
+
abi: import_viem14.erc20Abi,
|
|
5499
5778
|
functionName: "transfer",
|
|
5500
|
-
args: [(0,
|
|
5779
|
+
args: [(0, import_viem14.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
5501
5780
|
}),
|
|
5502
5781
|
value: "0",
|
|
5503
5782
|
chainId
|
|
@@ -5540,7 +5819,7 @@ var YieldseekerAgent = class {
|
|
|
5540
5819
|
notifyFunding(onProgress, evidence);
|
|
5541
5820
|
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
5542
5821
|
}
|
|
5543
|
-
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
|
|
5822
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
5544
5823
|
let ownerTransferAttempted = false;
|
|
5545
5824
|
let evidence = {
|
|
5546
5825
|
agentId: this.id,
|
|
@@ -5558,7 +5837,7 @@ var YieldseekerAgent = class {
|
|
|
5558
5837
|
notifyFunding(onProgress, evidence);
|
|
5559
5838
|
}, () => {
|
|
5560
5839
|
ownerTransferAttempted = true;
|
|
5561
|
-
}, gasMode, onBeforeSubmission);
|
|
5840
|
+
}, gasMode, onBeforeSubmission, assertOperation);
|
|
5562
5841
|
} catch (error) {
|
|
5563
5842
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
|
|
5564
5843
|
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)))
|
|
@@ -5571,7 +5850,7 @@ var YieldseekerAgent = class {
|
|
|
5571
5850
|
throw error;
|
|
5572
5851
|
}
|
|
5573
5852
|
}
|
|
5574
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
|
|
5853
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
5575
5854
|
this.assertChain(chainId);
|
|
5576
5855
|
this.assertAsset(asset);
|
|
5577
5856
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -5643,7 +5922,7 @@ var YieldseekerAgent = class {
|
|
|
5643
5922
|
);
|
|
5644
5923
|
const positionsRead = this.readWithdrawalPositions(state, chainId, context);
|
|
5645
5924
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
5646
|
-
const agentWallet = (0,
|
|
5925
|
+
const agentWallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
|
|
5647
5926
|
const readOwnerFunds = async (blockNumber) => {
|
|
5648
5927
|
const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5649
5928
|
address: agentWallet,
|
|
@@ -5652,18 +5931,18 @@ var YieldseekerAgent = class {
|
|
|
5652
5931
|
blockNumber
|
|
5653
5932
|
}), chainId, "eth_call", this.id);
|
|
5654
5933
|
this.assertSession(generation, chainId, asset);
|
|
5655
|
-
if (!(0,
|
|
5934
|
+
if (!(0, import_viem14.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker))
|
|
5656
5935
|
throw this.invalidResponse("wallet fee tracker");
|
|
5657
5936
|
const [idle2, fees] = await Promise.all([
|
|
5658
5937
|
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5659
5938
|
address: metadata.address,
|
|
5660
|
-
abi:
|
|
5939
|
+
abi: import_viem14.erc20Abi,
|
|
5661
5940
|
functionName: "balanceOf",
|
|
5662
5941
|
args: [agentWallet],
|
|
5663
5942
|
blockNumber
|
|
5664
5943
|
}), chainId, "eth_call", this.id),
|
|
5665
5944
|
withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
5666
|
-
address: (0,
|
|
5945
|
+
address: (0, import_viem14.getAddress)(feeTracker),
|
|
5667
5946
|
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
5668
5947
|
functionName: "getFeesOwed",
|
|
5669
5948
|
args: [agentWallet],
|
|
@@ -5689,7 +5968,7 @@ var YieldseekerAgent = class {
|
|
|
5689
5968
|
const initialFunds = funds;
|
|
5690
5969
|
const idle = initialFunds.idle;
|
|
5691
5970
|
const maxLiquidity = amount === void 0 ? funds : void 0;
|
|
5692
|
-
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.
|
|
5971
|
+
let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()))];
|
|
5693
5972
|
let positionsStatus = "empty";
|
|
5694
5973
|
const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
|
|
5695
5974
|
validatePositions(positions);
|
|
@@ -5697,36 +5976,38 @@ var YieldseekerAgent = class {
|
|
|
5697
5976
|
const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
|
|
5698
5977
|
const knownPositions = /* @__PURE__ */ new Map();
|
|
5699
5978
|
const rememberPositions = (values) => {
|
|
5700
|
-
for (const position2 of values) knownPositions.set(position2.
|
|
5979
|
+
for (const position2 of values) knownPositions.set(position2.vaultOption.address.toLowerCase(), position2);
|
|
5701
5980
|
};
|
|
5702
5981
|
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
5703
5982
|
const verifiedPositions = [];
|
|
5704
5983
|
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
5705
|
-
let
|
|
5706
|
-
const checks = this.
|
|
5984
|
+
let active2 = true;
|
|
5985
|
+
const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
|
|
5707
5986
|
const reads = values.map(async (position2, index) => {
|
|
5708
|
-
const address = (0,
|
|
5987
|
+
const address = (0, import_viem14.getAddress)(position2.vaultOption.address);
|
|
5709
5988
|
try {
|
|
5710
|
-
const group = (await checks).slice(index *
|
|
5711
|
-
if (!
|
|
5989
|
+
const group = (await checks).slice(index * 2, index * 2 + 2);
|
|
5990
|
+
if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
|
|
5712
5991
|
this.assertSession(generation, chainId, asset);
|
|
5713
|
-
const [underlying, shares
|
|
5714
|
-
if (typeof underlying !== "string" || !(0,
|
|
5715
|
-
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw:
|
|
5716
|
-
if (shares > 0n
|
|
5992
|
+
const [underlying, shares] = group.map((value) => value.result);
|
|
5993
|
+
if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
|
|
5994
|
+
const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: "0" };
|
|
5995
|
+
if (shares > 0n) {
|
|
5717
5996
|
let positiveValue = false;
|
|
5718
5997
|
const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
|
|
5719
5998
|
try {
|
|
5720
5999
|
const value = await this.getReceiptClient().readContract({
|
|
5721
6000
|
address,
|
|
5722
|
-
abi:
|
|
6001
|
+
abi: import_viem14.erc4626Abi,
|
|
5723
6002
|
functionName,
|
|
5724
6003
|
args: [shares],
|
|
5725
6004
|
blockNumber
|
|
5726
6005
|
});
|
|
5727
|
-
if (typeof value !== "bigint" || value < 0n) return null;
|
|
6006
|
+
if (!active2 || typeof value !== "bigint" || value < 0n) return null;
|
|
6007
|
+
this.assertSession(generation, chainId, asset);
|
|
5728
6008
|
if (value > 0n && !positiveValue) {
|
|
5729
6009
|
positiveValue = true;
|
|
6010
|
+
verified.withdrawableAssetsRaw = value.toString();
|
|
5730
6011
|
verifiedPositions.push(verified);
|
|
5731
6012
|
}
|
|
5732
6013
|
return value;
|
|
@@ -5734,7 +6015,12 @@ var YieldseekerAgent = class {
|
|
|
5734
6015
|
return null;
|
|
5735
6016
|
}
|
|
5736
6017
|
}));
|
|
5737
|
-
if (positiveValue)
|
|
6018
|
+
if (positiveValue) {
|
|
6019
|
+
const redemptionValue = valuations[1];
|
|
6020
|
+
if (redemptionValue !== null && redemptionValue > 0n)
|
|
6021
|
+
verified.withdrawableAssetsRaw = redemptionValue.toString();
|
|
6022
|
+
return verified;
|
|
6023
|
+
}
|
|
5738
6024
|
if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
|
|
5739
6025
|
zeroValuePositions.add(verified);
|
|
5740
6026
|
}
|
|
@@ -5745,7 +6031,7 @@ var YieldseekerAgent = class {
|
|
|
5745
6031
|
}
|
|
5746
6032
|
});
|
|
5747
6033
|
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
5748
|
-
|
|
6034
|
+
active2 = false;
|
|
5749
6035
|
this.assertSession(generation, chainId, asset);
|
|
5750
6036
|
return {
|
|
5751
6037
|
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
@@ -5757,13 +6043,14 @@ var YieldseekerAgent = class {
|
|
|
5757
6043
|
const response = await this.walletRequest(
|
|
5758
6044
|
state,
|
|
5759
6045
|
chainId,
|
|
5760
|
-
this.agentPath(context, "
|
|
6046
|
+
this.agentPath(context, "vault-positions")
|
|
5761
6047
|
);
|
|
5762
6048
|
this.assertSession(generation, chainId, asset);
|
|
5763
|
-
if (!Array.isArray(response?.
|
|
5764
|
-
return validatePositions(response.
|
|
6049
|
+
if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
|
|
6050
|
+
return validatePositions(response.vaultPositions);
|
|
5765
6051
|
};
|
|
5766
6052
|
if (amount === void 0) {
|
|
6053
|
+
rememberPositions(priorPositions);
|
|
5767
6054
|
rememberPositions(funds.indexedPositions);
|
|
5768
6055
|
rememberPositions(positions);
|
|
5769
6056
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
@@ -5790,7 +6077,7 @@ var YieldseekerAgent = class {
|
|
|
5790
6077
|
throw new OwneyError(
|
|
5791
6078
|
"WITHDRAW_FAILED",
|
|
5792
6079
|
"Yieldseeker vault funds are not currently withdrawable.",
|
|
5793
|
-
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.
|
|
6080
|
+
{ asset, positionsStatus, remainingPositions: positions.map((position2) => position2.vaultOption.address) },
|
|
5794
6081
|
this.id
|
|
5795
6082
|
);
|
|
5796
6083
|
}
|
|
@@ -5839,6 +6126,7 @@ var YieldseekerAgent = class {
|
|
|
5839
6126
|
let latestUnwindBlock;
|
|
5840
6127
|
const unwind = async (position2, assetsRaw) => {
|
|
5841
6128
|
this.assertSession(generation, chainId, asset);
|
|
6129
|
+
assertOperation?.();
|
|
5842
6130
|
let unwindTransactionHash;
|
|
5843
6131
|
try {
|
|
5844
6132
|
const response = await this.walletRequest(
|
|
@@ -5847,7 +6135,8 @@ var YieldseekerAgent = class {
|
|
|
5847
6135
|
this.agentPath(context, "withdraw-from-position"),
|
|
5848
6136
|
{
|
|
5849
6137
|
method: "POST",
|
|
5850
|
-
body: { chainId, vaultAddress: position2.
|
|
6138
|
+
body: { chainId, vaultAddress: position2.vaultOption.address, assetsRaw: assetsRaw.toString() },
|
|
6139
|
+
assertCurrent: assertOperation
|
|
5851
6140
|
}
|
|
5852
6141
|
);
|
|
5853
6142
|
if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
|
|
@@ -5866,7 +6155,7 @@ var YieldseekerAgent = class {
|
|
|
5866
6155
|
...error instanceof OwneyError ? error.details : {},
|
|
5867
6156
|
withdrawalPhase: "unwind",
|
|
5868
6157
|
reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
|
|
5869
|
-
vaultAddress: position2.
|
|
6158
|
+
vaultAddress: position2.vaultOption.address,
|
|
5870
6159
|
unwindTransactionHash,
|
|
5871
6160
|
ownerTransferSubmitted: false
|
|
5872
6161
|
},
|
|
@@ -5879,7 +6168,7 @@ var YieldseekerAgent = class {
|
|
|
5879
6168
|
};
|
|
5880
6169
|
if (amount === void 0) {
|
|
5881
6170
|
for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
|
|
5882
|
-
const before = new Map(positions.map((position2) => [position2.
|
|
6171
|
+
const before = new Map(positions.map((position2) => [position2.vaultOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
|
|
5883
6172
|
let unwound = false;
|
|
5884
6173
|
for (const position2 of positions) {
|
|
5885
6174
|
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
@@ -5896,14 +6185,16 @@ var YieldseekerAgent = class {
|
|
|
5896
6185
|
if (catalog) rememberPositions(catalog);
|
|
5897
6186
|
this.assertSession(generation, chainId, asset);
|
|
5898
6187
|
if (proof.verified) {
|
|
5899
|
-
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.
|
|
5900
|
-
} else if (catalog)
|
|
5901
|
-
|
|
6188
|
+
positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.vaultOption.address.toLowerCase()))];
|
|
6189
|
+
} else if (catalog) {
|
|
6190
|
+
const checked = new Set(proof.positions.map((position2) => position2.vaultOption.address.toLowerCase()));
|
|
6191
|
+
positions = [...proof.positions, ...catalog.filter((position2) => !checked.has(position2.vaultOption.address.toLowerCase()))];
|
|
6192
|
+
} else {
|
|
5902
6193
|
positionsStatus = "unavailable";
|
|
5903
6194
|
break;
|
|
5904
6195
|
}
|
|
5905
6196
|
positionsStatus = hasPositions(positions) ? "remaining" : "empty";
|
|
5906
|
-
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.
|
|
6197
|
+
if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.vaultOption.address.toLowerCase()))) break;
|
|
5907
6198
|
}
|
|
5908
6199
|
} else {
|
|
5909
6200
|
for (const position2 of positions) {
|
|
@@ -5916,7 +6207,7 @@ var YieldseekerAgent = class {
|
|
|
5916
6207
|
}
|
|
5917
6208
|
}
|
|
5918
6209
|
if (amount !== void 0 && remaining > 0n) {
|
|
5919
|
-
throw this.invalidResponse("
|
|
6210
|
+
throw this.invalidResponse("vault positions", {
|
|
5920
6211
|
reason: "Withdrawable positions could not cover the request.",
|
|
5921
6212
|
remaining: remaining.toString()
|
|
5922
6213
|
});
|
|
@@ -5948,15 +6239,22 @@ var YieldseekerAgent = class {
|
|
|
5948
6239
|
delta: maxSettlementBasis.delta - payout,
|
|
5949
6240
|
expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
|
|
5950
6241
|
} : this.snapshotMovement(state, chainId, context, -payout);
|
|
5951
|
-
const account = (0,
|
|
6242
|
+
const account = (0, import_viem14.getAddress)(state.walletAddress);
|
|
5952
6243
|
let receipt;
|
|
5953
6244
|
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" };
|
|
6245
|
+
if (amount === void 0) base7.maxCompletion = {
|
|
6246
|
+
userId: context.user.userId,
|
|
6247
|
+
agentId: context.agent.agentId,
|
|
6248
|
+
vaults: [...knownPositions.keys()],
|
|
6249
|
+
inventoryComplete: funds.complete
|
|
6250
|
+
};
|
|
6251
|
+
if (base7.maxCompletion) notifyFunding(onProgress, base7);
|
|
5954
6252
|
let submissionEvidence = base7;
|
|
5955
|
-
const data = amount === void 0 ? (0,
|
|
6253
|
+
const data = amount === void 0 ? (0, import_viem14.encodeFunctionData)({
|
|
5956
6254
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5957
6255
|
functionName: "withdrawAllAssetToUser",
|
|
5958
6256
|
args: [account, metadata.address]
|
|
5959
|
-
}) : (0,
|
|
6257
|
+
}) : (0, import_viem14.encodeFunctionData)({
|
|
5960
6258
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
5961
6259
|
functionName: "withdrawAssetToUser",
|
|
5962
6260
|
args: [account, metadata.address, payout]
|
|
@@ -5968,11 +6266,12 @@ var YieldseekerAgent = class {
|
|
|
5968
6266
|
client: this.getReceiptClient(),
|
|
5969
6267
|
apiKey: this.owneyApiKey,
|
|
5970
6268
|
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
6269
|
+
assertCurrent: assertOperation,
|
|
5971
6270
|
yieldseekerSignature: await this.auth.getToken(state, chainId),
|
|
5972
6271
|
refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
|
|
5973
6272
|
userId: context.user.userId,
|
|
5974
6273
|
agentId: context.agent.agentId,
|
|
5975
|
-
agentWallet: (0,
|
|
6274
|
+
agentWallet: (0, import_viem14.getAddress)(context.wallet.walletAddress),
|
|
5976
6275
|
asset: metadata.address,
|
|
5977
6276
|
callData: data,
|
|
5978
6277
|
onPending: (pending) => {
|
|
@@ -5980,6 +6279,7 @@ var YieldseekerAgent = class {
|
|
|
5980
6279
|
notifyFunding(onProgress, submissionEvidence);
|
|
5981
6280
|
},
|
|
5982
6281
|
onBeforeSubmission: async (pending) => {
|
|
6282
|
+
assertOperation?.();
|
|
5983
6283
|
await onBeforeSubmission?.({ ...base7, ...pending });
|
|
5984
6284
|
onOwnerTransferAttempted?.();
|
|
5985
6285
|
}
|
|
@@ -5988,15 +6288,26 @@ var YieldseekerAgent = class {
|
|
|
5988
6288
|
receipt = submitted.receipt;
|
|
5989
6289
|
} else {
|
|
5990
6290
|
onOwnerTransferAttempted?.();
|
|
5991
|
-
txHash = await this.submitTransaction(
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6291
|
+
txHash = await this.submitTransaction(
|
|
6292
|
+
state,
|
|
6293
|
+
chainId,
|
|
6294
|
+
{
|
|
6295
|
+
from: account,
|
|
6296
|
+
to: (0, import_viem14.getAddress)(context.wallet.walletAddress),
|
|
6297
|
+
data,
|
|
6298
|
+
value: "0",
|
|
6299
|
+
chainId
|
|
6300
|
+
},
|
|
6301
|
+
(hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
|
|
6302
|
+
(value) => {
|
|
6303
|
+
receipt = value;
|
|
6304
|
+
},
|
|
6305
|
+
async () => {
|
|
6306
|
+
assertOperation?.();
|
|
6307
|
+
await onBeforeSubmission?.(base7);
|
|
6308
|
+
assertOperation?.();
|
|
6309
|
+
}
|
|
6310
|
+
);
|
|
6000
6311
|
}
|
|
6001
6312
|
const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
|
|
6002
6313
|
notifyFunding(onProgress, evidence);
|
|
@@ -6041,7 +6352,7 @@ var YieldseekerAgent = class {
|
|
|
6041
6352
|
asset,
|
|
6042
6353
|
positionsStatus,
|
|
6043
6354
|
remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
|
|
6044
|
-
vaultAddress: position2.
|
|
6355
|
+
vaultAddress: position2.vaultOption.address,
|
|
6045
6356
|
sharesRaw: position2.assetsRaw,
|
|
6046
6357
|
withdrawableAmountRaw: position2.withdrawableAssetsRaw
|
|
6047
6358
|
})) : void 0,
|
|
@@ -6050,6 +6361,7 @@ var YieldseekerAgent = class {
|
|
|
6050
6361
|
this.id
|
|
6051
6362
|
);
|
|
6052
6363
|
}
|
|
6364
|
+
if (evidence.maxCompletion) evidence.maxCompletion.vaults = [...knownPositions.keys()];
|
|
6053
6365
|
if (!proof.verified) completion = "unverified";
|
|
6054
6366
|
}
|
|
6055
6367
|
if (explicitEmptyPayout)
|
|
@@ -6082,7 +6394,7 @@ var YieldseekerAgent = class {
|
|
|
6082
6394
|
}
|
|
6083
6395
|
}
|
|
6084
6396
|
}
|
|
6085
|
-
/**
|
|
6397
|
+
/** Fresh position amounts for withdrawal planning, net of reserved fees. */
|
|
6086
6398
|
async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
|
|
6087
6399
|
this.assertChain(chainId);
|
|
6088
6400
|
this.assertAsset(asset);
|
|
@@ -6107,18 +6419,76 @@ var YieldseekerAgent = class {
|
|
|
6107
6419
|
);
|
|
6108
6420
|
return proof.available;
|
|
6109
6421
|
}
|
|
6110
|
-
|
|
6111
|
-
|
|
6422
|
+
/** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
|
|
6423
|
+
async verifyWithdrawalCompletion(state, evidence) {
|
|
6424
|
+
const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
|
|
6425
|
+
const proof = evidence.maxCompletion;
|
|
6426
|
+
if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem14.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown("PAYOUT_PROOF_UNAVAILABLE");
|
|
6427
|
+
this.assertAsset(evidence.token);
|
|
6428
|
+
const asset = evidence.token;
|
|
6429
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
6430
|
+
const signature = this.auth.peekToken(state, evidence.chainId);
|
|
6431
|
+
if (!signature) return unknown("AUTHENTICATION_REQUIRED");
|
|
6432
|
+
let reason = "CATALOG_UNAVAILABLE";
|
|
6433
|
+
try {
|
|
6434
|
+
return await bounded(async () => {
|
|
6435
|
+
const response = await this.api.request(
|
|
6436
|
+
`/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
|
|
6437
|
+
{ signature }
|
|
6438
|
+
);
|
|
6439
|
+
if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
|
|
6440
|
+
const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
|
|
6441
|
+
const vaults = new Set([...proof.vaults, ...positions.map((p) => p.vaultOption.address)].map((a) => a.toLowerCase()));
|
|
6442
|
+
if (!proof.inventoryComplete) {
|
|
6443
|
+
const catalog = await this.loadVaultOptions(asset);
|
|
6444
|
+
const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem14.isAddress)(o.address));
|
|
6445
|
+
if (!eligible.length) return unknown("INVENTORY_INCOMPLETE");
|
|
6446
|
+
eligible.forEach((o) => vaults.add(o.address.toLowerCase()));
|
|
6447
|
+
}
|
|
6448
|
+
if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults].some((a) => !(0, import_viem14.isAddress)(a)))
|
|
6449
|
+
return unknown("INVENTORY_INVALID");
|
|
6450
|
+
reason = "VAULT_PROOF_UNAVAILABLE";
|
|
6451
|
+
const blockNumber = BigInt(evidence.blockNumber);
|
|
6452
|
+
const addresses = [...vaults];
|
|
6453
|
+
const shares = await this.readVaultShares(addresses.map((address) => ({ address })), evidence.agentWallet, blockNumber);
|
|
6454
|
+
const checks = await Promise.all(addresses.map(async (address, i) => {
|
|
6455
|
+
const group = shares.slice(i * 2, i * 2 + 2);
|
|
6456
|
+
if (group.length !== 2 || group.some((r) => r.status !== "success")) return "unavailable";
|
|
6457
|
+
const [underlying, value] = group.map((r) => r.result);
|
|
6458
|
+
if (typeof underlying !== "string" || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof value !== "bigint" || value < 0n) return "unavailable";
|
|
6459
|
+
if (value === 0n) return "complete";
|
|
6460
|
+
const amounts = await Promise.allSettled(["convertToAssets", "previewRedeem"].map((functionName) => this.getReceiptClient().readContract({
|
|
6461
|
+
address: (0, import_viem14.getAddress)(address),
|
|
6462
|
+
abi: import_viem14.erc4626Abi,
|
|
6463
|
+
functionName,
|
|
6464
|
+
args: [value],
|
|
6465
|
+
blockNumber
|
|
6466
|
+
})));
|
|
6467
|
+
if (amounts.some((r) => r.status === "fulfilled" && typeof r.value === "bigint" && r.value > 0n)) return "remaining";
|
|
6468
|
+
return amounts.every((r) => r.status === "fulfilled" && r.value === 0n) ? "complete" : "unavailable";
|
|
6469
|
+
}));
|
|
6470
|
+
const status = checks.includes("remaining") ? "remaining" : checks.includes("unavailable") ? "unavailable" : "complete";
|
|
6471
|
+
return {
|
|
6472
|
+
status,
|
|
6473
|
+
checkedVaults: addresses.length,
|
|
6474
|
+
...status === "unavailable" ? { reason } : status === "remaining" ? { reason: "VAULT_REMAINDER" } : {}
|
|
6475
|
+
};
|
|
6476
|
+
}, 8e3, () => new Error("MAX proof read timed out"));
|
|
6477
|
+
} catch {
|
|
6478
|
+
return unknown(reason);
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
async readVaultShares(options, wallet, blockNumber) {
|
|
6482
|
+
const owner = (0, import_viem14.getAddress)(wallet);
|
|
6112
6483
|
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));
|
|
6113
6484
|
const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
|
|
6114
6485
|
blockNumber,
|
|
6115
6486
|
allowFailure: true,
|
|
6116
6487
|
contracts: batch.flatMap((option) => {
|
|
6117
|
-
const address = (0,
|
|
6488
|
+
const address = (0, import_viem14.getAddress)(option.address);
|
|
6118
6489
|
return [
|
|
6119
|
-
{ address, abi:
|
|
6120
|
-
{ address, abi:
|
|
6121
|
-
{ address, abi: import_viem13.erc4626Abi, functionName: "maxWithdraw", args: [owner] }
|
|
6490
|
+
{ address, abi: import_viem14.erc4626Abi, functionName: "asset" },
|
|
6491
|
+
{ address, abi: import_viem14.erc20Abi, functionName: "balanceOf", args: [owner] }
|
|
6122
6492
|
];
|
|
6123
6493
|
})
|
|
6124
6494
|
})));
|
|
@@ -6128,23 +6498,23 @@ var YieldseekerAgent = class {
|
|
|
6128
6498
|
const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
|
|
6129
6499
|
const seen = /* @__PURE__ */ new Set();
|
|
6130
6500
|
for (const value of values) {
|
|
6131
|
-
const option = value?.
|
|
6132
|
-
if (!option || typeof option.address !== "string" || !(0,
|
|
6133
|
-
throw this.invalidResponse("
|
|
6501
|
+
const option = value?.vaultOption;
|
|
6502
|
+
if (!option || typeof option.address !== "string" || !(0, import_viem14.isAddress)(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))
|
|
6503
|
+
throw this.invalidResponse("vault positions");
|
|
6134
6504
|
seen.add(option.address.toLowerCase());
|
|
6135
6505
|
}
|
|
6136
6506
|
return values;
|
|
6137
6507
|
}
|
|
6138
6508
|
withdrawalVaultHints(state, chainId, context) {
|
|
6139
6509
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6140
|
-
const positions = this.readCache.get(`positions:${key2}`)?.value?.
|
|
6141
|
-
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.
|
|
6510
|
+
const positions = this.readCache.get(`positions:${key2}`)?.value?.vaultPositions ?? [];
|
|
6511
|
+
const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
|
|
6142
6512
|
const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
|
|
6143
6513
|
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()));
|
|
6144
|
-
for (const option of this.
|
|
6145
|
-
if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !(0,
|
|
6514
|
+
for (const option of this.vaultOptions.get(context.asset)?.value ?? []) {
|
|
6515
|
+
if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !(0, import_viem14.isAddress)(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
|
|
6146
6516
|
hints.set(option.address.toLowerCase(), {
|
|
6147
|
-
|
|
6517
|
+
vaultOption: option,
|
|
6148
6518
|
assetsRaw: "0",
|
|
6149
6519
|
assetsBase: "0",
|
|
6150
6520
|
assetsUsd: 0,
|
|
@@ -6178,7 +6548,7 @@ var YieldseekerAgent = class {
|
|
|
6178
6548
|
async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
|
|
6179
6549
|
const generation = this.readGeneration;
|
|
6180
6550
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
6181
|
-
const version = this.
|
|
6551
|
+
const version = this.liquidityVersions.get(contextKey) ?? 0;
|
|
6182
6552
|
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
6183
6553
|
const unavailable = () => new OwneyError(
|
|
6184
6554
|
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
@@ -6186,31 +6556,30 @@ var YieldseekerAgent = class {
|
|
|
6186
6556
|
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
6187
6557
|
this.id
|
|
6188
6558
|
);
|
|
6189
|
-
let
|
|
6559
|
+
let active2 = true;
|
|
6190
6560
|
let knownProof;
|
|
6191
|
-
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.
|
|
6561
|
+
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
6192
6562
|
const assertReadCurrent = () => {
|
|
6193
6563
|
this.assertSession(generation, chainId, context.asset);
|
|
6194
|
-
if (!
|
|
6564
|
+
if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
|
|
6195
6565
|
};
|
|
6196
6566
|
const read2 = async () => {
|
|
6197
6567
|
const client = this.getReceiptClient();
|
|
6198
6568
|
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
6199
|
-
const wallet = (0,
|
|
6569
|
+
const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
|
|
6200
6570
|
assertReadCurrent();
|
|
6201
6571
|
if (typeof blockNumber !== "bigint") throw unavailable();
|
|
6202
6572
|
const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
|
|
6203
6573
|
const candidates = /* @__PURE__ */ new Map();
|
|
6204
6574
|
for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
|
|
6205
|
-
candidates.set(position2.
|
|
6206
|
-
for (const position2 of indexedPositions) candidates.set(position2.
|
|
6575
|
+
candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
|
|
6576
|
+
for (const position2 of indexedPositions) candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
|
|
6207
6577
|
const options = [...candidates.values()];
|
|
6208
6578
|
if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
6209
|
-
const checksRead = this.readVaultLiquidity(options, wallet, blockNumber).then((value) => ({ value }), (error) => ({ error }));
|
|
6210
6579
|
const [idle, feeTracker] = await Promise.all([
|
|
6211
6580
|
client.readContract({
|
|
6212
6581
|
address: metadata.address,
|
|
6213
|
-
abi:
|
|
6582
|
+
abi: import_viem14.erc20Abi,
|
|
6214
6583
|
functionName: "balanceOf",
|
|
6215
6584
|
args: [wallet],
|
|
6216
6585
|
blockNumber
|
|
@@ -6222,10 +6591,10 @@ var YieldseekerAgent = class {
|
|
|
6222
6591
|
blockNumber
|
|
6223
6592
|
})
|
|
6224
6593
|
]);
|
|
6225
|
-
if (typeof idle !== "bigint" || idle < 0n || !(0,
|
|
6594
|
+
if (typeof idle !== "bigint" || idle < 0n || !(0, import_viem14.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
|
|
6226
6595
|
assertReadCurrent();
|
|
6227
6596
|
const fees = await client.readContract({
|
|
6228
|
-
address: (0,
|
|
6597
|
+
address: (0, import_viem14.getAddress)(feeTracker),
|
|
6229
6598
|
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
6230
6599
|
functionName: "getFeesOwed",
|
|
6231
6600
|
args: [wallet],
|
|
@@ -6233,7 +6602,7 @@ var YieldseekerAgent = class {
|
|
|
6233
6602
|
});
|
|
6234
6603
|
if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
|
|
6235
6604
|
const verified = [];
|
|
6236
|
-
const
|
|
6605
|
+
const resolvedVaults = [];
|
|
6237
6606
|
let deployed = 0n;
|
|
6238
6607
|
const proof = (complete) => ({
|
|
6239
6608
|
blockNumber,
|
|
@@ -6241,53 +6610,102 @@ var YieldseekerAgent = class {
|
|
|
6241
6610
|
fees,
|
|
6242
6611
|
available: idle + deployed > fees ? idle + deployed - fees : 0n,
|
|
6243
6612
|
positions: [...verified],
|
|
6244
|
-
|
|
6613
|
+
resolvedVaults: [...resolvedVaults],
|
|
6245
6614
|
indexedPositions,
|
|
6246
6615
|
complete
|
|
6247
6616
|
});
|
|
6248
6617
|
assertReadCurrent();
|
|
6249
6618
|
knownProof = proof(false);
|
|
6250
6619
|
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6251
|
-
const
|
|
6620
|
+
const indexed = new Map(indexedPositions.map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
|
|
6621
|
+
const indexedValue = indexedPositions.reduce((sum, position2) => sum + BigInt(position2.withdrawableAssetsRaw), 0n);
|
|
6622
|
+
const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.vaultOption.address.toLowerCase()));
|
|
6623
|
+
const unvaluedPosition = indexedPositions.some((position2) => BigInt(position2.assetsRaw) > 0n && BigInt(position2.withdrawableAssetsRaw) === 0n);
|
|
6624
|
+
const indexedAvailable = idle + indexedValue > fees ? idle + indexedValue - fees : 0n;
|
|
6625
|
+
if (indexedValue > 0n && !missingKnownPosition && !unvaluedPosition && (requested === void 0 || indexedAvailable >= requested)) {
|
|
6626
|
+
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
6627
|
+
for (const position2 of indexedPositions) if (BigInt(position2.withdrawableAssetsRaw) > 0n)
|
|
6628
|
+
known.add(position2.vaultOption.address.toLowerCase());
|
|
6629
|
+
this.balanceVaults.set(contextKey, known);
|
|
6630
|
+
return {
|
|
6631
|
+
blockNumber,
|
|
6632
|
+
idle,
|
|
6633
|
+
fees,
|
|
6634
|
+
available: indexedAvailable,
|
|
6635
|
+
positions: indexedPositions,
|
|
6636
|
+
resolvedVaults: [...indexed.keys()],
|
|
6637
|
+
indexedPositions,
|
|
6638
|
+
complete: true
|
|
6639
|
+
};
|
|
6640
|
+
}
|
|
6641
|
+
const collect = async (options2, results) => {
|
|
6252
6642
|
assertReadCurrent();
|
|
6253
6643
|
const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
|
|
6254
6644
|
this.balanceVaults.set(contextKey, known);
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
const
|
|
6259
|
-
if (
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
return;
|
|
6264
|
-
}
|
|
6265
|
-
const [underlying, shares, withdrawable] = group.map((value) => value.result);
|
|
6266
|
-
if (typeof underlying !== "string" || !(0, import_viem13.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
|
|
6267
|
-
valid2 = false;
|
|
6268
|
-
return;
|
|
6269
|
-
}
|
|
6270
|
-
verifiedVaults.push(option.address.toLowerCase());
|
|
6271
|
-
deployed += withdrawable;
|
|
6272
|
-
verified.push({
|
|
6273
|
-
yieldOption: option,
|
|
6645
|
+
const valid2 = await Promise.all(options2.map(async (option, index) => {
|
|
6646
|
+
const group = results.slice(index * 2, index * 2 + 2);
|
|
6647
|
+
if (group.length !== 2 || group.some((value2) => value2.status !== "success")) return false;
|
|
6648
|
+
const [underlying, shares] = group.map((value2) => value2.result);
|
|
6649
|
+
if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
|
|
6650
|
+
if (shares > 0n) known.add(option.address.toLowerCase());
|
|
6651
|
+
const position2 = {
|
|
6652
|
+
vaultOption: option,
|
|
6274
6653
|
assetsRaw: shares.toString(),
|
|
6275
6654
|
assetsBase: "0",
|
|
6276
6655
|
assetsUsd: 0,
|
|
6277
|
-
withdrawableAssetsRaw:
|
|
6278
|
-
withdrawableAssetsBase:
|
|
6279
|
-
}
|
|
6280
|
-
|
|
6281
|
-
|
|
6656
|
+
withdrawableAssetsRaw: "0",
|
|
6657
|
+
withdrawableAssetsBase: "0"
|
|
6658
|
+
};
|
|
6659
|
+
resolvedVaults.push(option.address.toLowerCase());
|
|
6660
|
+
verified.push(position2);
|
|
6661
|
+
knownProof = proof(false);
|
|
6662
|
+
if (shares === 0n) return true;
|
|
6663
|
+
let preview;
|
|
6664
|
+
try {
|
|
6665
|
+
const value2 = await client.readContract({
|
|
6666
|
+
address: (0, import_viem14.getAddress)(option.address),
|
|
6667
|
+
abi: import_viem14.erc4626Abi,
|
|
6668
|
+
functionName: "previewRedeem",
|
|
6669
|
+
args: [shares],
|
|
6670
|
+
blockNumber
|
|
6671
|
+
});
|
|
6672
|
+
if (typeof value2 === "bigint" && value2 >= 0n) preview = value2;
|
|
6673
|
+
} catch {
|
|
6674
|
+
}
|
|
6675
|
+
assertReadCurrent();
|
|
6676
|
+
let value = preview;
|
|
6677
|
+
if (value === void 0 || value === 0n) {
|
|
6678
|
+
try {
|
|
6679
|
+
const converted = await client.readContract({
|
|
6680
|
+
address: (0, import_viem14.getAddress)(option.address),
|
|
6681
|
+
abi: import_viem14.erc4626Abi,
|
|
6682
|
+
functionName: "convertToAssets",
|
|
6683
|
+
args: [shares],
|
|
6684
|
+
blockNumber
|
|
6685
|
+
});
|
|
6686
|
+
if (typeof converted === "bigint" && converted >= 0n && (converted > 0n || preview === 0n)) value = converted;
|
|
6687
|
+
else value = void 0;
|
|
6688
|
+
} catch {
|
|
6689
|
+
value = void 0;
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
assertReadCurrent();
|
|
6693
|
+
if (value === void 0) return false;
|
|
6694
|
+
position2.withdrawableAssetsRaw = value.toString();
|
|
6695
|
+
position2.withdrawableAssetsBase = value.toString();
|
|
6696
|
+
deployed += value;
|
|
6697
|
+
knownProof = proof(false);
|
|
6698
|
+
return true;
|
|
6699
|
+
}));
|
|
6700
|
+
return valid2.every(Boolean);
|
|
6282
6701
|
};
|
|
6283
|
-
const checks = await
|
|
6702
|
+
const checks = await this.readVaultShares(options, wallet, blockNumber);
|
|
6284
6703
|
assertReadCurrent();
|
|
6285
|
-
|
|
6286
|
-
const allKnownVerified = collect(options, checks.value);
|
|
6704
|
+
const allKnownVerified = await collect(options, checks);
|
|
6287
6705
|
knownProof = proof(false);
|
|
6288
|
-
|
|
6706
|
+
if (requested !== void 0 && knownProof.available >= requested) return knownProof;
|
|
6289
6707
|
const discrepancy = verified.some((position2) => {
|
|
6290
|
-
const row = indexed.get(position2.
|
|
6708
|
+
const row = indexed.get(position2.vaultOption.address.toLowerCase());
|
|
6291
6709
|
return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
|
|
6292
6710
|
});
|
|
6293
6711
|
const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
|
|
@@ -6296,16 +6714,16 @@ var YieldseekerAgent = class {
|
|
|
6296
6714
|
assertReadCurrent();
|
|
6297
6715
|
return proof(true);
|
|
6298
6716
|
}
|
|
6299
|
-
const catalog = await this.
|
|
6717
|
+
const catalog = await this.loadVaultOptions(context.asset).catch(() => void 0);
|
|
6300
6718
|
assertReadCurrent();
|
|
6301
6719
|
const eligible = /* @__PURE__ */ new Map();
|
|
6302
6720
|
for (const option of catalog ?? []) {
|
|
6303
|
-
if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && (0,
|
|
6721
|
+
if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem14.isAddress)(option.address))
|
|
6304
6722
|
eligible.set(option.address.toLowerCase(), option);
|
|
6305
6723
|
}
|
|
6306
6724
|
const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
|
|
6307
6725
|
if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
|
|
6308
|
-
const allExtraVerified = collect(extra, await this.
|
|
6726
|
+
const allExtraVerified = await collect(extra, await this.readVaultShares(extra, wallet, blockNumber));
|
|
6309
6727
|
assertReadCurrent();
|
|
6310
6728
|
const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
|
|
6311
6729
|
if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
|
|
@@ -6322,7 +6740,7 @@ var YieldseekerAgent = class {
|
|
|
6322
6740
|
if (error instanceof OwneyError) throw error;
|
|
6323
6741
|
throw unavailable();
|
|
6324
6742
|
} finally {
|
|
6325
|
-
|
|
6743
|
+
active2 = false;
|
|
6326
6744
|
if (timer) clearTimeout(timer);
|
|
6327
6745
|
}
|
|
6328
6746
|
}
|
|
@@ -6336,7 +6754,7 @@ var YieldseekerAgent = class {
|
|
|
6336
6754
|
this.balanceContexts.set(read2, { context, generation: this.readGeneration });
|
|
6337
6755
|
const key2 = this.contextKey(state, chainId, context.asset);
|
|
6338
6756
|
const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
|
|
6339
|
-
for (const position2 of context.positions) known.add(position2.
|
|
6757
|
+
for (const position2 of context.positions) known.add(position2.vaultOption.address.toLowerCase());
|
|
6340
6758
|
this.balanceVaults.set(key2, known);
|
|
6341
6759
|
}
|
|
6342
6760
|
return balance;
|
|
@@ -6351,7 +6769,7 @@ var YieldseekerAgent = class {
|
|
|
6351
6769
|
const key2 = this.contextKey(state, evidence.chainId, evidence.token);
|
|
6352
6770
|
const generation = this.readGeneration, version = this.portfolioVersion(key2);
|
|
6353
6771
|
if (read2.readVersion !== version) return void 0;
|
|
6354
|
-
const wallet = (0,
|
|
6772
|
+
const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
|
|
6355
6773
|
const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
|
|
6356
6774
|
const client = this.getReceiptClient();
|
|
6357
6775
|
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
@@ -6359,10 +6777,10 @@ var YieldseekerAgent = class {
|
|
|
6359
6777
|
const vaults = [...this.balanceVaults.get(key2) ?? []];
|
|
6360
6778
|
const [invested, idle, tracker] = await Promise.all([
|
|
6361
6779
|
remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
|
|
6362
|
-
client.readContract({ address: asset.address, abi:
|
|
6780
|
+
client.readContract({ address: asset.address, abi: import_viem14.erc20Abi, functionName: "balanceOf", args: [wallet], blockNumber }),
|
|
6363
6781
|
client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
|
|
6364
6782
|
]);
|
|
6365
|
-
if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !(0,
|
|
6783
|
+
if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !(0, import_viem14.isAddress)(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
|
|
6366
6784
|
const fees = await client.readContract({
|
|
6367
6785
|
address: tracker,
|
|
6368
6786
|
abi: YIELDSEEKER_FEE_TRACKER_ABI,
|
|
@@ -6375,7 +6793,7 @@ var YieldseekerAgent = class {
|
|
|
6375
6793
|
const total = idle + invested > fees ? idle + invested - fees : 0n;
|
|
6376
6794
|
const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
|
|
6377
6795
|
if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
|
|
6378
|
-
const amount = (0,
|
|
6796
|
+
const amount = (0, import_viem14.parseUnits)(displayed.amount, asset.decimals);
|
|
6379
6797
|
if (amount < 0n) return void 0;
|
|
6380
6798
|
const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
|
|
6381
6799
|
const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
|
|
@@ -6430,9 +6848,9 @@ var YieldseekerAgent = class {
|
|
|
6430
6848
|
if (!context) return void 0;
|
|
6431
6849
|
const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
6432
6850
|
address: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
6433
|
-
abi:
|
|
6851
|
+
abi: import_viem14.erc20Abi,
|
|
6434
6852
|
functionName: "balanceOf",
|
|
6435
|
-
args: [(0,
|
|
6853
|
+
args: [(0, import_viem14.getAddress)(context.wallet.walletAddress)]
|
|
6436
6854
|
}), chainId, "eth_call", this.id);
|
|
6437
6855
|
this.assertSession(generation, chainId, asset);
|
|
6438
6856
|
if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
|
|
@@ -6474,13 +6892,13 @@ var YieldseekerAgent = class {
|
|
|
6474
6892
|
});
|
|
6475
6893
|
const catalog = await Promise.all(
|
|
6476
6894
|
[...new Set(contexts.map((context) => context.asset))].map(
|
|
6477
|
-
(contextAsset) => this.
|
|
6895
|
+
(contextAsset) => this.loadVaultOptions(contextAsset)
|
|
6478
6896
|
)
|
|
6479
6897
|
);
|
|
6480
6898
|
const vaultAddresses = new Set(
|
|
6481
6899
|
catalog.flat().filter(
|
|
6482
|
-
(
|
|
6483
|
-
).map((
|
|
6900
|
+
(vaultOption) => vaultOption.chainId === chainId && (0, import_viem14.isAddress)(vaultOption.address)
|
|
6901
|
+
).map((vaultOption) => vaultOption.address.toLowerCase())
|
|
6484
6902
|
);
|
|
6485
6903
|
return mapYieldseekerHistory(contexts, {
|
|
6486
6904
|
limit: options?.limit ?? 10,
|
|
@@ -6505,30 +6923,30 @@ var YieldseekerAgent = class {
|
|
|
6505
6923
|
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
6506
6924
|
const values = await Promise.all(
|
|
6507
6925
|
assets.map(async (asset) => {
|
|
6508
|
-
return { asset,
|
|
6926
|
+
return { asset, vaultOptions: await this.loadVaultOptions(asset) };
|
|
6509
6927
|
})
|
|
6510
6928
|
);
|
|
6511
6929
|
return mapYieldseekerAgentApy(values, days);
|
|
6512
6930
|
}
|
|
6513
|
-
async
|
|
6514
|
-
const cached = this.
|
|
6931
|
+
async loadVaultOptions(asset) {
|
|
6932
|
+
const cached = this.vaultOptions.get(asset);
|
|
6515
6933
|
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
6516
|
-
const pending = this.
|
|
6934
|
+
const pending = this.pendingVaultOptions.get(asset);
|
|
6517
6935
|
if (pending) return pending;
|
|
6518
6936
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
6519
6937
|
const request2 = this.api.request(
|
|
6520
|
-
`/chains/8453/assets/${metadata.address}/
|
|
6938
|
+
`/chains/8453/assets/${metadata.address}/vault-options`
|
|
6521
6939
|
).then((response) => {
|
|
6522
|
-
if (!Array.isArray(response?.
|
|
6523
|
-
throw this.invalidResponse("
|
|
6940
|
+
if (!Array.isArray(response?.vaultOptions)) {
|
|
6941
|
+
throw this.invalidResponse("vault options");
|
|
6524
6942
|
}
|
|
6525
|
-
this.
|
|
6526
|
-
expiresAt: Date.now() +
|
|
6527
|
-
value: response.
|
|
6943
|
+
this.vaultOptions.set(asset, {
|
|
6944
|
+
expiresAt: Date.now() + YIELDSEEKER_VAULT_OPTIONS_CACHE_MS,
|
|
6945
|
+
value: response.vaultOptions
|
|
6528
6946
|
});
|
|
6529
|
-
return response.
|
|
6530
|
-
}).finally(() => this.
|
|
6531
|
-
this.
|
|
6947
|
+
return response.vaultOptions;
|
|
6948
|
+
}).finally(() => this.pendingVaultOptions.delete(asset));
|
|
6949
|
+
this.pendingVaultOptions.set(asset, request2);
|
|
6532
6950
|
return request2;
|
|
6533
6951
|
}
|
|
6534
6952
|
userKey(state, chainId) {
|
|
@@ -6608,7 +7026,7 @@ var YieldseekerAgent = class {
|
|
|
6608
7026
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
6609
7027
|
).then((walletResponse) => {
|
|
6610
7028
|
this.assertSession(generation, chainId, asset);
|
|
6611
|
-
if (!walletResponse?.agentWallet || !(0,
|
|
7029
|
+
if (!walletResponse?.agentWallet || !(0, import_viem14.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
6612
7030
|
throw this.invalidResponse("agent wallet");
|
|
6613
7031
|
}
|
|
6614
7032
|
const context = {
|
|
@@ -6638,7 +7056,7 @@ var YieldseekerAgent = class {
|
|
|
6638
7056
|
this.users.set(key2, persisted);
|
|
6639
7057
|
return persisted;
|
|
6640
7058
|
}
|
|
6641
|
-
const walletAddress = (0,
|
|
7059
|
+
const walletAddress = (0, import_viem14.getAddress)(state.walletAddress);
|
|
6642
7060
|
const generation = this.readGeneration;
|
|
6643
7061
|
let user = null;
|
|
6644
7062
|
try {
|
|
@@ -6838,9 +7256,10 @@ var YieldseekerAgent = class {
|
|
|
6838
7256
|
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
6839
7257
|
};
|
|
6840
7258
|
}
|
|
6841
|
-
advancePortfolioVersion(contextKey) {
|
|
7259
|
+
advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
|
|
6842
7260
|
const version = this.portfolioVersion(contextKey) + 1;
|
|
6843
7261
|
this.portfolioVersions.set(contextKey, version);
|
|
7262
|
+
if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
|
|
6844
7263
|
this.snapshotFailures.delete(contextKey);
|
|
6845
7264
|
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
6846
7265
|
const key2 = `${kind}:${contextKey}`;
|
|
@@ -6888,7 +7307,7 @@ var YieldseekerAgent = class {
|
|
|
6888
7307
|
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
6889
7308
|
const failure = this.snapshotFailures.get(contextKey);
|
|
6890
7309
|
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
6891
|
-
this.advancePortfolioVersion(contextKey);
|
|
7310
|
+
this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
|
|
6892
7311
|
}
|
|
6893
7312
|
const version = this.portfolioVersion(contextKey);
|
|
6894
7313
|
const generation = this.readGeneration;
|
|
@@ -6971,17 +7390,17 @@ var YieldseekerAgent = class {
|
|
|
6971
7390
|
}
|
|
6972
7391
|
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
6973
7392
|
const [positions, historic, actions] = await Promise.all([
|
|
6974
|
-
options.includePositions === false ? Promise.resolve({
|
|
7393
|
+
options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
|
|
6975
7394
|
`positions:${contextKey}`,
|
|
6976
7395
|
this.portfolioCacheMs(contextKey),
|
|
6977
7396
|
async () => {
|
|
6978
7397
|
const response = await this.walletRequest(
|
|
6979
7398
|
state,
|
|
6980
7399
|
chainId,
|
|
6981
|
-
this.agentPath(context, "
|
|
7400
|
+
this.agentPath(context, "vault-positions")
|
|
6982
7401
|
);
|
|
6983
|
-
if (!Array.isArray(response?.
|
|
6984
|
-
throw this.invalidResponse("
|
|
7402
|
+
if (!Array.isArray(response?.vaultPositions)) {
|
|
7403
|
+
throw this.invalidResponse("vault positions");
|
|
6985
7404
|
}
|
|
6986
7405
|
return response;
|
|
6987
7406
|
}
|
|
@@ -7006,7 +7425,7 @@ var YieldseekerAgent = class {
|
|
|
7006
7425
|
) : Promise.resolve(void 0)
|
|
7007
7426
|
]);
|
|
7008
7427
|
return {
|
|
7009
|
-
positions: positions.
|
|
7428
|
+
positions: positions.vaultPositions,
|
|
7010
7429
|
...historic?.position ? { historic: historic.position } : {},
|
|
7011
7430
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
7012
7431
|
};
|
|
@@ -7020,7 +7439,7 @@ var YieldseekerAgent = class {
|
|
|
7020
7439
|
this.agentPath(context, "deploy"),
|
|
7021
7440
|
{ method: "POST", body: {} }
|
|
7022
7441
|
);
|
|
7023
|
-
if (!deployed?.agentWallet || !(0,
|
|
7442
|
+
if (!deployed?.agentWallet || !(0, import_viem14.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
7024
7443
|
throw this.invalidResponse("agent deployment", {
|
|
7025
7444
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
7026
7445
|
});
|
|
@@ -7077,10 +7496,10 @@ var YieldseekerAgent = class {
|
|
|
7077
7496
|
}
|
|
7078
7497
|
async providerRequest(state, chainId, path, options = {}) {
|
|
7079
7498
|
this.assertChain(chainId);
|
|
7080
|
-
const request2 = (signature2) =>
|
|
7081
|
-
|
|
7082
|
-
signature: signature2
|
|
7083
|
-
}
|
|
7499
|
+
const request2 = (signature2) => {
|
|
7500
|
+
options.assertCurrent?.();
|
|
7501
|
+
return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
|
|
7502
|
+
};
|
|
7084
7503
|
let signature = await this.auth.getToken(state, chainId);
|
|
7085
7504
|
try {
|
|
7086
7505
|
return await request2(signature);
|
|
@@ -7129,18 +7548,18 @@ var YieldseekerAgent = class {
|
|
|
7129
7548
|
return hash3;
|
|
7130
7549
|
}
|
|
7131
7550
|
this.assertTransaction(transaction, state, chainId);
|
|
7132
|
-
const account = (0,
|
|
7133
|
-
const walletClient = (0,
|
|
7551
|
+
const account = (0, import_viem14.getAddress)(state.walletAddress);
|
|
7552
|
+
const walletClient = (0, import_viem14.createWalletClient)({
|
|
7134
7553
|
account,
|
|
7135
7554
|
chain: import_chains4.base,
|
|
7136
|
-
transport: onBeforeSubmission ? (0,
|
|
7555
|
+
transport: onBeforeSubmission ? (0, import_viem14.custom)({ request: async (args) => {
|
|
7137
7556
|
if (args.method === "eth_sendTransaction") await onBeforeSubmission();
|
|
7138
7557
|
return state.provider.request(args);
|
|
7139
|
-
} }, { retryCount: 0 }) : (0,
|
|
7558
|
+
} }, { retryCount: 0 }) : (0, import_viem14.custom)(state.provider)
|
|
7140
7559
|
});
|
|
7141
|
-
const walletChainClient = (0,
|
|
7560
|
+
const walletChainClient = (0, import_viem14.createPublicClient)({
|
|
7142
7561
|
chain: import_chains4.base,
|
|
7143
|
-
transport: (0,
|
|
7562
|
+
transport: (0, import_viem14.custom)(state.provider)
|
|
7144
7563
|
});
|
|
7145
7564
|
await ensureWalletOnChain(
|
|
7146
7565
|
walletChainClient,
|
|
@@ -7150,7 +7569,7 @@ var YieldseekerAgent = class {
|
|
|
7150
7569
|
const hash2 = await walletClient.sendTransaction({
|
|
7151
7570
|
account,
|
|
7152
7571
|
chain: import_chains4.base,
|
|
7153
|
-
to: (0,
|
|
7572
|
+
to: (0, import_viem14.getAddress)(transaction.to),
|
|
7154
7573
|
data: transaction.data,
|
|
7155
7574
|
value: BigInt(transaction.value)
|
|
7156
7575
|
});
|
|
@@ -7254,12 +7673,12 @@ var YieldseekerAgent = class {
|
|
|
7254
7673
|
return receipt;
|
|
7255
7674
|
}
|
|
7256
7675
|
assertTransaction(transaction, state, chainId) {
|
|
7257
|
-
if (!transaction || typeof transaction.from !== "string" || !(0,
|
|
7676
|
+
if (!transaction || typeof transaction.from !== "string" || !(0, import_viem14.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem14.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem14.getAddress)(transaction.from) !== (0, import_viem14.getAddress)(state.walletAddress)) {
|
|
7258
7677
|
throw this.invalidResponse("transaction");
|
|
7259
7678
|
}
|
|
7260
7679
|
}
|
|
7261
7680
|
assertAgent(agent) {
|
|
7262
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0,
|
|
7681
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem14.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
|
|
7263
7682
|
throw this.invalidResponse("agent");
|
|
7264
7683
|
}
|
|
7265
7684
|
}
|
|
@@ -7382,45 +7801,57 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
|
7382
7801
|
// src/lib/swap/swap-api.ts
|
|
7383
7802
|
async function request(baseUrl, apiKey, path, init) {
|
|
7384
7803
|
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
7385
|
-
const
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7804
|
+
const controller = new AbortController();
|
|
7805
|
+
try {
|
|
7806
|
+
return await bounded(async () => {
|
|
7807
|
+
const res = await fetch(url, {
|
|
7808
|
+
signal: controller.signal,
|
|
7809
|
+
method: init?.method ?? "GET",
|
|
7810
|
+
headers: {
|
|
7811
|
+
"Content-Type": "application/json",
|
|
7812
|
+
"x-owney-api-key": apiKey
|
|
7813
|
+
},
|
|
7814
|
+
...init ? { body: JSON.stringify(init.body) } : {}
|
|
7815
|
+
});
|
|
7816
|
+
if (!res.ok) {
|
|
7817
|
+
const text = await res.text().catch(() => "");
|
|
7818
|
+
if (res.status === 429) {
|
|
7819
|
+
throw new OwneyError(
|
|
7820
|
+
"SWAP_RATE_LIMITED",
|
|
7821
|
+
"Swap provider is rate limiting, retry shortly",
|
|
7822
|
+
{ statusCode: res.status }
|
|
7823
|
+
);
|
|
7824
|
+
}
|
|
7825
|
+
if (res.status === 403) {
|
|
7826
|
+
throw new OwneyError(
|
|
7827
|
+
"SWAP_DISABLED",
|
|
7828
|
+
"Swap is not enabled for this organization",
|
|
7829
|
+
{ statusCode: res.status }
|
|
7830
|
+
);
|
|
7831
|
+
}
|
|
7832
|
+
throw new OwneyError(
|
|
7833
|
+
"SWAP_REQUEST_FAILED",
|
|
7834
|
+
`Swap API error ${res.status}: ${text}`,
|
|
7835
|
+
{ statusCode: res.status, responseBody: text }
|
|
7836
|
+
);
|
|
7837
|
+
}
|
|
7838
|
+
const json = await res.json();
|
|
7839
|
+
if (!json.success) {
|
|
7840
|
+
throw new OwneyError(
|
|
7841
|
+
"SWAP_REQUEST_FAILED",
|
|
7842
|
+
`Swap API request failed: ${json.message ?? "unknown error"}`,
|
|
7843
|
+
{ message: json.message }
|
|
7844
|
+
);
|
|
7845
|
+
}
|
|
7846
|
+
return json.data;
|
|
7847
|
+
}, 2e4, () => new OwneyError(
|
|
7418
7848
|
"SWAP_REQUEST_FAILED",
|
|
7419
|
-
|
|
7420
|
-
{
|
|
7421
|
-
);
|
|
7849
|
+
"Swap request timed out. Its outcome may still need confirmation.",
|
|
7850
|
+
{ requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
|
|
7851
|
+
));
|
|
7852
|
+
} finally {
|
|
7853
|
+
controller.abort();
|
|
7422
7854
|
}
|
|
7423
|
-
return json.data;
|
|
7424
7855
|
}
|
|
7425
7856
|
function createSwapApi(baseUrl, apiKey) {
|
|
7426
7857
|
return {
|
|
@@ -7544,7 +7975,7 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
7544
7975
|
}
|
|
7545
7976
|
|
|
7546
7977
|
// src/lib/swap/swap.rpc.ts
|
|
7547
|
-
var
|
|
7978
|
+
var import_viem15 = require("viem");
|
|
7548
7979
|
var DEFAULT_RPC_URLS = {
|
|
7549
7980
|
// NOT cloudflare-eth.com, which used to lead this list. It answers
|
|
7550
7981
|
// eth_chainId happily, errors on eth_blockNumber, and — the part that
|
|
@@ -7584,10 +8015,10 @@ var DEFAULT_RPC_URLS = {
|
|
|
7584
8015
|
};
|
|
7585
8016
|
function swapReadTransport(chainId, overrides) {
|
|
7586
8017
|
const override = overrides?.[chainId];
|
|
7587
|
-
if (override) return (0,
|
|
8018
|
+
if (override) return (0, import_viem15.http)(override);
|
|
7588
8019
|
const urls = DEFAULT_RPC_URLS[chainId];
|
|
7589
|
-
if (!urls || urls.length === 0) return (0,
|
|
7590
|
-
return (0,
|
|
8020
|
+
if (!urls || urls.length === 0) return (0, import_viem15.http)();
|
|
8021
|
+
return (0, import_viem15.fallback)(urls.map((url) => (0, import_viem15.http)(url)));
|
|
7591
8022
|
}
|
|
7592
8023
|
function receiptTimeoutMs(chainId) {
|
|
7593
8024
|
return chainId === 1 ? 6e5 : 18e4;
|
|
@@ -7716,8 +8147,12 @@ async function awaitBalanceIncrease(options) {
|
|
|
7716
8147
|
let lastError;
|
|
7717
8148
|
for (; ; ) {
|
|
7718
8149
|
try {
|
|
7719
|
-
const balance = await
|
|
7720
|
-
|
|
8150
|
+
const balance = await bounded(
|
|
8151
|
+
readBalance,
|
|
8152
|
+
Math.max(1, Math.min(1e4, deadline - Date.now())),
|
|
8153
|
+
() => new Error("Balance observation timed out")
|
|
8154
|
+
);
|
|
8155
|
+
if (Date.now() <= deadline && balance > baseline) {
|
|
7721
8156
|
const arrived = balance - baseline;
|
|
7722
8157
|
debugLog("owney-sdk", "arrival: funds landed", {
|
|
7723
8158
|
arrived: arrived.toString()
|
|
@@ -7743,12 +8178,12 @@ async function awaitBalanceIncrease(options) {
|
|
|
7743
8178
|
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
7744
8179
|
} : { arrived: null };
|
|
7745
8180
|
}
|
|
7746
|
-
await sleep(pollMs);
|
|
8181
|
+
await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
|
|
7747
8182
|
}
|
|
7748
8183
|
}
|
|
7749
8184
|
|
|
7750
8185
|
// src/lib/swap/swap.secrets.ts
|
|
7751
|
-
var
|
|
8186
|
+
var import_viem16 = require("viem");
|
|
7752
8187
|
var SECRET_BYTES = 32;
|
|
7753
8188
|
function randomBytes(length) {
|
|
7754
8189
|
const bytes = new Uint8Array(length);
|
|
@@ -7770,9 +8205,9 @@ function mintSecrets(count) {
|
|
|
7770
8205
|
const secrets = [];
|
|
7771
8206
|
const secretHashes = [];
|
|
7772
8207
|
for (let i = 0; i < count; i++) {
|
|
7773
|
-
const secret = (0,
|
|
8208
|
+
const secret = (0, import_viem16.toHex)(randomBytes(SECRET_BYTES));
|
|
7774
8209
|
secrets.push(secret);
|
|
7775
|
-
secretHashes.push((0,
|
|
8210
|
+
secretHashes.push((0, import_viem16.keccak256)(secret));
|
|
7776
8211
|
}
|
|
7777
8212
|
return { secrets, secretHashes };
|
|
7778
8213
|
}
|
|
@@ -7796,7 +8231,7 @@ var DEFAULT_POLL_MS2 = 5e3;
|
|
|
7796
8231
|
var MAX_BACKOFF_MS = 3e4;
|
|
7797
8232
|
var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
7798
8233
|
var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
|
|
7799
|
-
async function
|
|
8234
|
+
async function runFusionOrderInternal(deps, options) {
|
|
7800
8235
|
const {
|
|
7801
8236
|
orderHash,
|
|
7802
8237
|
secrets,
|
|
@@ -7805,39 +8240,51 @@ async function runFusionOrder(deps, options) {
|
|
|
7805
8240
|
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
7806
8241
|
} = options;
|
|
7807
8242
|
const deadline = deps.now() + timeoutMs;
|
|
8243
|
+
let partial = options.hasPartialFill === true;
|
|
8244
|
+
const timeout = () => new OwneyError(
|
|
8245
|
+
"SWAP_REQUEST_FAILED",
|
|
8246
|
+
"Swap settlement is not confirmed yet. Check its status before starting another swap.",
|
|
8247
|
+
{ orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
|
|
8248
|
+
);
|
|
8249
|
+
const assertTime = () => {
|
|
8250
|
+
if (deps.now() >= deadline) throw timeout();
|
|
8251
|
+
};
|
|
8252
|
+
const read2 = async (work) => {
|
|
8253
|
+
assertTime();
|
|
8254
|
+
const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
|
|
8255
|
+
assertTime();
|
|
8256
|
+
return result;
|
|
8257
|
+
};
|
|
8258
|
+
const pause = async (ms) => {
|
|
8259
|
+
assertTime();
|
|
8260
|
+
await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
|
|
8261
|
+
assertTime();
|
|
8262
|
+
};
|
|
7808
8263
|
let failures = 0;
|
|
7809
8264
|
const published = /* @__PURE__ */ new Set();
|
|
7810
8265
|
onStage?.("swapping");
|
|
7811
8266
|
for (; ; ) {
|
|
7812
|
-
|
|
7813
|
-
throw new OwneyError(
|
|
7814
|
-
"SWAP_REQUEST_FAILED",
|
|
7815
|
-
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
7816
|
-
// Marks the one failure that means "we stopped watching a live order",
|
|
7817
|
-
// as opposed to a verdict or a prompt nobody answered. The executor
|
|
7818
|
-
// checks the wallet before believing it: resolvers do not stop filling
|
|
7819
|
-
// because we stopped looking (ROUT-242, comment 15980).
|
|
7820
|
-
{ orderHash, settlementUnconfirmed: true }
|
|
7821
|
-
);
|
|
7822
|
-
}
|
|
8267
|
+
assertTime();
|
|
7823
8268
|
let ready;
|
|
7824
8269
|
try {
|
|
7825
|
-
ready = await deps.readyForSecrets(orderHash);
|
|
8270
|
+
ready = await read2(() => deps.readyForSecrets(orderHash));
|
|
7826
8271
|
} catch {
|
|
7827
8272
|
ready = {};
|
|
7828
8273
|
}
|
|
8274
|
+
assertTime();
|
|
7829
8275
|
for (const fill of ready.fills ?? []) {
|
|
8276
|
+
assertTime();
|
|
7830
8277
|
if (published.has(fill.idx)) continue;
|
|
7831
8278
|
const secret = secrets[fill.idx];
|
|
7832
8279
|
if (secret === void 0) {
|
|
7833
8280
|
throw new OwneyError(
|
|
7834
8281
|
"SWAP_REQUEST_FAILED",
|
|
7835
8282
|
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
7836
|
-
{ orderHash, fillIndex: fill.idx }
|
|
8283
|
+
{ orderHash, fillIndex: fill.idx, settlementUnconfirmed: true, outcome: "submitted-unknown" }
|
|
7837
8284
|
);
|
|
7838
8285
|
}
|
|
7839
8286
|
try {
|
|
7840
|
-
await deps.submitSecret(orderHash, secret);
|
|
8287
|
+
await read2(() => deps.submitSecret(orderHash, secret));
|
|
7841
8288
|
published.add(fill.idx);
|
|
7842
8289
|
} catch {
|
|
7843
8290
|
failures += 1;
|
|
@@ -7845,29 +8292,34 @@ async function runFusionOrder(deps, options) {
|
|
|
7845
8292
|
}
|
|
7846
8293
|
let status;
|
|
7847
8294
|
try {
|
|
7848
|
-
|
|
8295
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8296
|
+
status = state.status;
|
|
8297
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7849
8298
|
failures = 0;
|
|
7850
8299
|
} catch {
|
|
7851
8300
|
failures += 1;
|
|
7852
|
-
await
|
|
8301
|
+
await pause(backoffFor(failures, pollIntervalMs));
|
|
7853
8302
|
continue;
|
|
7854
8303
|
}
|
|
7855
8304
|
if (status === "refunding") onStage?.("refunding");
|
|
7856
8305
|
if (isSwapTerminal(status)) {
|
|
7857
|
-
if (status === "executed") {
|
|
8306
|
+
if (status === "executed" || status === "filled") {
|
|
7858
8307
|
onStage?.("swapped");
|
|
7859
8308
|
return { status, filled: true };
|
|
7860
8309
|
}
|
|
7861
|
-
if (status === "refunded") onStage?.("refunded");
|
|
8310
|
+
if (status === "refunded") onStage?.(partial ? "swap-partial" : "refunded");
|
|
7862
8311
|
throw new OwneyError(
|
|
7863
8312
|
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7864
|
-
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.",
|
|
7865
|
-
{ orderHash, status }
|
|
8313
|
+
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.",
|
|
8314
|
+
{ orderHash, status, outcome: status === "refunded" && !partial ? "refunded" : partial ? "partially-filled" : "refund-pending" }
|
|
7866
8315
|
);
|
|
7867
8316
|
}
|
|
7868
|
-
await
|
|
8317
|
+
await pause(pollIntervalMs);
|
|
7869
8318
|
}
|
|
7870
8319
|
}
|
|
8320
|
+
function runFusionOrder(deps, options) {
|
|
8321
|
+
return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
|
|
8322
|
+
}
|
|
7871
8323
|
|
|
7872
8324
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
7873
8325
|
var DEFAULT_POLL_MS3 = 5e3;
|
|
@@ -7892,7 +8344,7 @@ function explain(status) {
|
|
|
7892
8344
|
return "The swap did not complete. Nothing was exchanged.";
|
|
7893
8345
|
}
|
|
7894
8346
|
}
|
|
7895
|
-
async function
|
|
8347
|
+
async function runSameChainOrderInternal(deps, options) {
|
|
7896
8348
|
const {
|
|
7897
8349
|
orderHash,
|
|
7898
8350
|
onStage,
|
|
@@ -7900,23 +8352,39 @@ async function runSameChainOrder(deps, options) {
|
|
|
7900
8352
|
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
7901
8353
|
} = options;
|
|
7902
8354
|
const deadline = deps.now() + timeoutMs;
|
|
8355
|
+
let partial = options.hasPartialFill === true;
|
|
8356
|
+
const timeout = () => new OwneyError(
|
|
8357
|
+
"SWAP_REQUEST_FAILED",
|
|
8358
|
+
"Swap settlement is not confirmed yet. Check its status before starting another swap.",
|
|
8359
|
+
{ orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
|
|
8360
|
+
);
|
|
8361
|
+
const assertTime = () => {
|
|
8362
|
+
if (deps.now() >= deadline) throw timeout();
|
|
8363
|
+
};
|
|
8364
|
+
const read2 = async (work) => {
|
|
8365
|
+
assertTime();
|
|
8366
|
+
const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
|
|
8367
|
+
assertTime();
|
|
8368
|
+
return result;
|
|
8369
|
+
};
|
|
8370
|
+
const pause = async (ms) => {
|
|
8371
|
+
assertTime();
|
|
8372
|
+
await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
|
|
8373
|
+
assertTime();
|
|
8374
|
+
};
|
|
7903
8375
|
let failures = 0;
|
|
7904
8376
|
onStage?.("swapping");
|
|
7905
8377
|
for (; ; ) {
|
|
7906
|
-
|
|
7907
|
-
throw new OwneyError(
|
|
7908
|
-
"SWAP_ORDER_EXPIRED",
|
|
7909
|
-
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
7910
|
-
{ orderHash }
|
|
7911
|
-
);
|
|
7912
|
-
}
|
|
8378
|
+
assertTime();
|
|
7913
8379
|
let status;
|
|
7914
8380
|
try {
|
|
7915
|
-
|
|
8381
|
+
const state = await read2(() => deps.orderStatus(orderHash));
|
|
8382
|
+
status = state.status;
|
|
8383
|
+
partial ||= status === "partially-filled" || !!state.fills?.length;
|
|
7916
8384
|
failures = 0;
|
|
7917
8385
|
} catch {
|
|
7918
8386
|
failures += 1;
|
|
7919
|
-
await
|
|
8387
|
+
await pause(backoffFor2(failures, pollIntervalMs));
|
|
7920
8388
|
continue;
|
|
7921
8389
|
}
|
|
7922
8390
|
if (isSwapTerminal(status)) {
|
|
@@ -7926,17 +8394,19 @@ async function runSameChainOrder(deps, options) {
|
|
|
7926
8394
|
}
|
|
7927
8395
|
throw new OwneyError(
|
|
7928
8396
|
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
7929
|
-
explain(status),
|
|
7930
|
-
{ orderHash, status }
|
|
8397
|
+
partial ? "The swap filled partly. Check the order and both balances before retrying." : explain(status),
|
|
8398
|
+
{ orderHash, status, outcome: partial ? "partially-filled" : "not-submitted" }
|
|
7931
8399
|
);
|
|
7932
8400
|
}
|
|
7933
|
-
await
|
|
8401
|
+
await pause(pollIntervalMs);
|
|
7934
8402
|
}
|
|
7935
8403
|
}
|
|
8404
|
+
function runSameChainOrder(deps, options) {
|
|
8405
|
+
return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
|
|
8406
|
+
}
|
|
7936
8407
|
|
|
7937
8408
|
// src/lib/swap/swap.secret-store.ts
|
|
7938
8409
|
var KEY_PREFIX4 = "owney.swap.order";
|
|
7939
|
-
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7940
8410
|
var storage4 = () => {
|
|
7941
8411
|
if (typeof window === "undefined") return null;
|
|
7942
8412
|
try {
|
|
@@ -7948,10 +8418,13 @@ var storage4 = () => {
|
|
|
7948
8418
|
var keyFor2 = (orderHash) => `${KEY_PREFIX4}.${orderHash}`;
|
|
7949
8419
|
function saveOrder(order) {
|
|
7950
8420
|
const store = storage4();
|
|
7951
|
-
if (!store) return;
|
|
8421
|
+
if (!store) return false;
|
|
7952
8422
|
try {
|
|
7953
|
-
|
|
8423
|
+
const value = JSON.stringify(order);
|
|
8424
|
+
store.setItem(keyFor2(order.orderHash), value);
|
|
8425
|
+
return store.getItem(keyFor2(order.orderHash)) === value;
|
|
7954
8426
|
} catch {
|
|
8427
|
+
return false;
|
|
7955
8428
|
}
|
|
7956
8429
|
}
|
|
7957
8430
|
function clearOrder(orderHash) {
|
|
@@ -7962,7 +8435,7 @@ function clearOrder(orderHash) {
|
|
|
7962
8435
|
} catch {
|
|
7963
8436
|
}
|
|
7964
8437
|
}
|
|
7965
|
-
function listOrders(
|
|
8438
|
+
function listOrders(_now = Date.now()) {
|
|
7966
8439
|
const store = storage4();
|
|
7967
8440
|
if (!store) return [];
|
|
7968
8441
|
const out = [];
|
|
@@ -7977,10 +8450,6 @@ function listOrders(now = Date.now()) {
|
|
|
7977
8450
|
if (!raw2) continue;
|
|
7978
8451
|
try {
|
|
7979
8452
|
const parsed = JSON.parse(raw2);
|
|
7980
|
-
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
7981
|
-
store.removeItem(key2);
|
|
7982
|
-
continue;
|
|
7983
|
-
}
|
|
7984
8453
|
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
7985
8454
|
out.push(parsed);
|
|
7986
8455
|
}
|
|
@@ -8021,12 +8490,11 @@ async function awaitWalletPrompt(prompt, what) {
|
|
|
8021
8490
|
if (timer) clearTimeout(timer);
|
|
8022
8491
|
}
|
|
8023
8492
|
}
|
|
8024
|
-
function stoppedWatching(error) {
|
|
8025
|
-
return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
|
|
8026
|
-
}
|
|
8027
8493
|
var DEFAULT_SLIPPAGE = 1;
|
|
8028
|
-
async function affordableAmount(deps, quoted) {
|
|
8029
|
-
const
|
|
8494
|
+
async function affordableAmount(deps, quoted, reserve = 0n) {
|
|
8495
|
+
const raw2 = await deps.readSourceBalance();
|
|
8496
|
+
const balance = raw2 > reserve ? raw2 - reserve : 0n;
|
|
8497
|
+
if (balance <= 0n) throw new OwneyError("SWAP_REQUEST_FAILED", "No spendable source funds remain for this swap.", { outcome: "not-submitted" });
|
|
8030
8498
|
if (balance >= quoted) return quoted;
|
|
8031
8499
|
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
8032
8500
|
quoted: quoted.toString(),
|
|
@@ -8037,48 +8505,72 @@ async function affordableAmount(deps, quoted) {
|
|
|
8037
8505
|
}
|
|
8038
8506
|
async function executeSwap(deps, options) {
|
|
8039
8507
|
const { quote, walletAddress, onStage } = options;
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8508
|
+
let active2 = true;
|
|
8509
|
+
const original = deps;
|
|
8510
|
+
const assertActive = () => {
|
|
8511
|
+
if (!active2) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
|
|
8512
|
+
};
|
|
8513
|
+
const call = async (work, write = false) => {
|
|
8514
|
+
assertActive();
|
|
8515
|
+
if (write) options.assertCurrent?.();
|
|
8516
|
+
const value = await bounded(
|
|
8517
|
+
work,
|
|
8518
|
+
write ? 3e5 : 2e4,
|
|
8519
|
+
() => new OwneyError(
|
|
8520
|
+
"SWAP_REQUEST_FAILED",
|
|
8521
|
+
"The swap step could not be confirmed. Check its status before retrying.",
|
|
8522
|
+
{ settlementUnconfirmed: write, outcome: write ? "submitted-unknown" : "not-submitted" }
|
|
8523
|
+
)
|
|
8524
|
+
);
|
|
8525
|
+
assertActive();
|
|
8526
|
+
if (write) options.assertCurrent?.();
|
|
8527
|
+
return value;
|
|
8528
|
+
};
|
|
8529
|
+
deps = {
|
|
8530
|
+
...original,
|
|
8531
|
+
api: {
|
|
8532
|
+
...original.api,
|
|
8533
|
+
buildOrder: (args) => call(() => original.api.buildOrder(args)),
|
|
8534
|
+
swapTx: (args) => call(() => original.api.swapTx(args)),
|
|
8535
|
+
submitOrder: (args) => call(() => original.api.submitOrder(args), true)
|
|
8536
|
+
},
|
|
8537
|
+
readTargetBalance: () => call(original.readTargetBalance),
|
|
8538
|
+
readSourceBalance: () => call(original.readSourceBalance),
|
|
8539
|
+
readAllowance: (spender) => call(() => original.readAllowance(spender)),
|
|
8540
|
+
ensureChain: (chain2) => call(() => original.ensureChain(chain2), true),
|
|
8541
|
+
approve: (spender, amount) => call(() => original.approve(spender, amount), true),
|
|
8542
|
+
sendTransaction: (tx) => call(() => original.sendTransaction(tx), true),
|
|
8543
|
+
signTypedData: (data) => call(() => original.signTypedData(data), true),
|
|
8544
|
+
...original.buildPermit ? { buildPermit: (spender, amount) => call(() => original.buildPermit(spender, amount), true) } : {}
|
|
8545
|
+
};
|
|
8051
8546
|
try {
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8547
|
+
debugLog("owney-sdk", "swap: start", {
|
|
8548
|
+
rail: quote.rail,
|
|
8549
|
+
from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
|
|
8550
|
+
to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
|
|
8551
|
+
expected: quote.dst.amount,
|
|
8552
|
+
floor: quote.dstAmountMin
|
|
8058
8553
|
});
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
if (railError) throw railError;
|
|
8068
|
-
throw new OwneyError(
|
|
8069
|
-
"SWAP_REQUEST_FAILED",
|
|
8070
|
-
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
8071
|
-
{ rail: quote.rail, ...result ?? {} }
|
|
8072
|
-
);
|
|
8073
|
-
}
|
|
8074
|
-
if (railError) {
|
|
8075
|
-
debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
|
|
8076
|
-
received: arrived.toString()
|
|
8554
|
+
const before = await deps.readTargetBalance();
|
|
8555
|
+
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
8556
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
8557
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
8558
|
+
readBalance: () => deps.readTargetBalance(),
|
|
8559
|
+
baseline: before,
|
|
8560
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
|
|
8561
|
+
...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
|
|
8077
8562
|
});
|
|
8078
|
-
|
|
8079
|
-
|
|
8563
|
+
if (arrived === null) {
|
|
8564
|
+
throw new OwneyError(
|
|
8565
|
+
"SWAP_REQUEST_FAILED",
|
|
8566
|
+
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
8567
|
+
{ rail: quote.rail, ...result, outcome: "filled", settlementUnconfirmed: true }
|
|
8568
|
+
);
|
|
8569
|
+
}
|
|
8570
|
+
return { received: arrived.toString(), ...result };
|
|
8571
|
+
} finally {
|
|
8572
|
+
active2 = false;
|
|
8080
8573
|
}
|
|
8081
|
-
return { received: arrived.toString(), ...result ?? {} };
|
|
8082
8574
|
}
|
|
8083
8575
|
async function runClassic(deps, options) {
|
|
8084
8576
|
const {
|
|
@@ -8089,7 +8581,7 @@ async function runClassic(deps, options) {
|
|
|
8089
8581
|
onStage
|
|
8090
8582
|
} = options;
|
|
8091
8583
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8092
|
-
|
|
8584
|
+
let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8093
8585
|
const swapTxRequest = {
|
|
8094
8586
|
from: {
|
|
8095
8587
|
chainId: quote.src.chainId,
|
|
@@ -8121,25 +8613,36 @@ async function runClassic(deps, options) {
|
|
|
8121
8613
|
"owney-sdk",
|
|
8122
8614
|
"swap: re-fetching classic calldata after approval"
|
|
8123
8615
|
);
|
|
8616
|
+
amount = await affordableAmount(deps, amount, options.sourceReserve);
|
|
8617
|
+
swapTxRequest.from.amount = amount.toString();
|
|
8124
8618
|
({ tx } = await deps.api.swapTx(swapTxRequest));
|
|
8619
|
+
if (await deps.readAllowance(tx.to) < amount)
|
|
8620
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed for the current spender.", { outcome: "not-submitted" });
|
|
8125
8621
|
}
|
|
8126
8622
|
}
|
|
8127
8623
|
onStage?.("signing");
|
|
8128
8624
|
await deps.ensureChain(quote.src.chainId);
|
|
8129
8625
|
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
8626
|
+
options.assertCurrent?.();
|
|
8627
|
+
options.onTransactionRequested?.();
|
|
8130
8628
|
const txHash = await deps.sendTransaction({
|
|
8131
8629
|
to: tx.to,
|
|
8132
8630
|
data: tx.data,
|
|
8133
8631
|
value: tx.value ?? "0"
|
|
8632
|
+
}).catch((error) => {
|
|
8633
|
+
if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
|
|
8634
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "The swap transaction was rejected in the wallet.", { outcome: "not-submitted" });
|
|
8635
|
+
throw error;
|
|
8134
8636
|
});
|
|
8135
8637
|
onStage?.("swapped");
|
|
8136
|
-
return { txHash };
|
|
8638
|
+
return { txHash, spent: amount.toString() };
|
|
8137
8639
|
}
|
|
8138
8640
|
async function runSameChainFusion(deps, options, walletAddress) {
|
|
8139
8641
|
const { quote, direction, onStage } = options;
|
|
8140
8642
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
8141
|
-
|
|
8142
|
-
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
8643
|
+
let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8644
|
+
const { permit, paid } = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
8645
|
+
amount = await authorisedAmount(deps, options, amount, paid);
|
|
8143
8646
|
onStage?.("building");
|
|
8144
8647
|
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
8145
8648
|
const built = await deps.api.buildOrder({
|
|
@@ -8163,36 +8666,42 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
8163
8666
|
"sign the swap"
|
|
8164
8667
|
);
|
|
8165
8668
|
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8669
|
+
options.assertCurrent?.();
|
|
8670
|
+
options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
|
|
8671
|
+
try {
|
|
8672
|
+
await deps.api.submitOrder({
|
|
8673
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
8674
|
+
// which does not know it and answers with an error that names nothing.
|
|
8675
|
+
rail: "fusion",
|
|
8676
|
+
srcChainId: quote.src.chainId,
|
|
8677
|
+
order: built.order,
|
|
8678
|
+
signature,
|
|
8679
|
+
quoteId: built.quoteId,
|
|
8680
|
+
...built.extension ? { extension: built.extension } : {}
|
|
8681
|
+
});
|
|
8682
|
+
} catch (error) {
|
|
8683
|
+
throw submissionUnknown(error, built.orderHash);
|
|
8684
|
+
}
|
|
8176
8685
|
try {
|
|
8177
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8686
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable: true });
|
|
8178
8687
|
} catch {
|
|
8179
8688
|
}
|
|
8180
8689
|
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
8181
8690
|
orderHash: built.orderHash,
|
|
8182
8691
|
...onStage ? { onStage } : {}
|
|
8183
8692
|
});
|
|
8184
|
-
return { orderHash: built.orderHash };
|
|
8693
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
8185
8694
|
}
|
|
8186
8695
|
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
8187
8696
|
const { quote, onStage } = options;
|
|
8188
|
-
if (!quote.spender || isNativeSource) return
|
|
8697
|
+
if (!quote.spender || isNativeSource) return { paid: false };
|
|
8189
8698
|
const current = await deps.readAllowance(quote.spender);
|
|
8190
8699
|
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
8191
8700
|
spender: quote.spender,
|
|
8192
8701
|
current: current.toString(),
|
|
8193
8702
|
needed: amount.toString()
|
|
8194
8703
|
});
|
|
8195
|
-
if (current >= amount) return
|
|
8704
|
+
if (current >= amount) return { paid: false };
|
|
8196
8705
|
onStage?.("approving");
|
|
8197
8706
|
await deps.ensureChain(quote.src.chainId);
|
|
8198
8707
|
const permit = quote.permitSupported ? await awaitWalletPrompt(
|
|
@@ -8203,11 +8712,18 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
8203
8712
|
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
8204
8713
|
bytes: (permit.length - 2) / 2
|
|
8205
8714
|
});
|
|
8206
|
-
return permit;
|
|
8715
|
+
return { permit, paid: false };
|
|
8207
8716
|
}
|
|
8208
8717
|
await deps.approve(quote.spender, MAX_UINT256);
|
|
8209
8718
|
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
8210
|
-
return
|
|
8719
|
+
return { paid: true };
|
|
8720
|
+
}
|
|
8721
|
+
async function authorisedAmount(deps, options, amount, paid) {
|
|
8722
|
+
if (!paid) return amount;
|
|
8723
|
+
const remaining = await affordableAmount(deps, amount, options.sourceReserve);
|
|
8724
|
+
if (!options.quote.spender || await deps.readAllowance(options.quote.spender) < remaining)
|
|
8725
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed.", { outcome: "not-submitted" });
|
|
8726
|
+
return remaining;
|
|
8211
8727
|
}
|
|
8212
8728
|
async function runFusion(deps, options, walletAddress) {
|
|
8213
8729
|
const { quote, direction, onStage } = options;
|
|
@@ -8219,8 +8735,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8219
8735
|
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
8220
8736
|
);
|
|
8221
8737
|
}
|
|
8222
|
-
|
|
8223
|
-
const permit = await authoriseSpend(deps, options, amount, false);
|
|
8738
|
+
let amount = await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
|
|
8739
|
+
const { permit, paid } = await authoriseSpend(deps, options, amount, false);
|
|
8740
|
+
amount = await authorisedAmount(deps, options, amount, paid);
|
|
8224
8741
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
8225
8742
|
onStage?.("building");
|
|
8226
8743
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -8239,8 +8756,9 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8239
8756
|
...direction ? { direction } : {},
|
|
8240
8757
|
...permit ? { permit } : {}
|
|
8241
8758
|
});
|
|
8242
|
-
saveOrder({
|
|
8759
|
+
const durable = saveOrder({
|
|
8243
8760
|
orderHash: built.orderHash,
|
|
8761
|
+
walletAddress,
|
|
8244
8762
|
secrets,
|
|
8245
8763
|
srcChainId: quote.src.chainId,
|
|
8246
8764
|
srcSymbol: quote.src.symbol,
|
|
@@ -8250,33 +8768,39 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8250
8768
|
createdAt: Date.now()
|
|
8251
8769
|
});
|
|
8252
8770
|
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
(
|
|
8260
|
-
|
|
8261
|
-
|
|
8771
|
+
let signature;
|
|
8772
|
+
try {
|
|
8773
|
+
onStage?.("signing");
|
|
8774
|
+
await deps.ensureChain(quote.src.chainId);
|
|
8775
|
+
signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
|
|
8776
|
+
options.assertCurrent?.();
|
|
8777
|
+
options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
|
|
8778
|
+
} catch (error) {
|
|
8779
|
+
clearOrder(built.orderHash);
|
|
8780
|
+
throw error;
|
|
8781
|
+
}
|
|
8262
8782
|
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8783
|
+
try {
|
|
8784
|
+
await deps.api.submitOrder({
|
|
8785
|
+
srcChainId: quote.src.chainId,
|
|
8786
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
8787
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
8788
|
+
order: built.order,
|
|
8789
|
+
signature,
|
|
8790
|
+
quoteId: built.quoteId,
|
|
8791
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
8792
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
8793
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
8794
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
8795
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
8796
|
+
...built.extension ? { extension: built.extension } : {}
|
|
8797
|
+
});
|
|
8798
|
+
} catch (error) {
|
|
8799
|
+
throw submissionUnknown(error, built.orderHash);
|
|
8800
|
+
}
|
|
8277
8801
|
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
8278
8802
|
try {
|
|
8279
|
-
options.onOrderSubmitted?.(built.orderHash);
|
|
8803
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
|
|
8280
8804
|
} catch {
|
|
8281
8805
|
}
|
|
8282
8806
|
try {
|
|
@@ -8286,13 +8810,25 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
8286
8810
|
...onStage ? { onStage } : {}
|
|
8287
8811
|
});
|
|
8288
8812
|
} catch (error) {
|
|
8289
|
-
if (error instanceof OwneyError &&
|
|
8813
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
8290
8814
|
clearOrder(built.orderHash);
|
|
8291
8815
|
}
|
|
8292
8816
|
throw error;
|
|
8293
8817
|
}
|
|
8294
8818
|
clearOrder(built.orderHash);
|
|
8295
|
-
return { orderHash: built.orderHash };
|
|
8819
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
8820
|
+
}
|
|
8821
|
+
function submissionUnknown(error, orderHash) {
|
|
8822
|
+
return new OwneyError(
|
|
8823
|
+
"SWAP_REQUEST_FAILED",
|
|
8824
|
+
"The order submission could not be confirmed. Check this order before starting another swap.",
|
|
8825
|
+
{
|
|
8826
|
+
orderHash,
|
|
8827
|
+
settlementUnconfirmed: true,
|
|
8828
|
+
outcome: "submitted-unknown",
|
|
8829
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
8830
|
+
}
|
|
8831
|
+
);
|
|
8296
8832
|
}
|
|
8297
8833
|
|
|
8298
8834
|
// src/lib/swap/swap.withdrawal-settlement.ts
|
|
@@ -8302,8 +8838,8 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
8302
8838
|
async function awaitWithdrawalPayouts(options) {
|
|
8303
8839
|
const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
|
|
8304
8840
|
const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
|
|
8305
|
-
const deadline = Date.now() + timeoutMs;
|
|
8306
|
-
let
|
|
8841
|
+
const deadline = options.deadline ?? Date.now() + timeoutMs;
|
|
8842
|
+
let active2 = true;
|
|
8307
8843
|
let timer;
|
|
8308
8844
|
const incomplete = (reason, agentId) => new OwneyError(
|
|
8309
8845
|
"WITHDRAW_SWAP_INCOMPLETE",
|
|
@@ -8321,7 +8857,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8321
8857
|
);
|
|
8322
8858
|
const assertActive = () => {
|
|
8323
8859
|
options.assertCurrent();
|
|
8324
|
-
if (!
|
|
8860
|
+
if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
|
|
8325
8861
|
};
|
|
8326
8862
|
const pause = async () => {
|
|
8327
8863
|
assertActive();
|
|
@@ -8332,10 +8868,11 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8332
8868
|
if (evidence.agentId !== id || evidence.chainId !== options.chainId || evidence.token !== options.token || evidence.walletAddress.toLowerCase() !== options.walletAddress.toLowerCase())
|
|
8333
8869
|
throw incomplete("PAYOUT_SCOPE_MISMATCH", id);
|
|
8334
8870
|
};
|
|
8335
|
-
const
|
|
8871
|
+
const record2 = (id, payout, evidence) => {
|
|
8336
8872
|
assertActive();
|
|
8337
8873
|
validate(id, evidence);
|
|
8338
8874
|
payout.evidence = evidence;
|
|
8875
|
+
payout.result.evidence = evidence;
|
|
8339
8876
|
if (evidence.transfer === "confirmed" && evidence.delivery === "confirmed") payout.amount = rawAmount(evidence.confirmedAmount);
|
|
8340
8877
|
options.onProgress(evidence);
|
|
8341
8878
|
};
|
|
@@ -8400,8 +8937,30 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8400
8937
|
if (payout.amount === void 0 || payout.amount <= 0n) throw incomplete("INVALID_PAYOUT_AMOUNT", id);
|
|
8401
8938
|
if (["yieldseeker", "zyfai"].includes(id) && (!hash(evidence.txHash) || rawAmount(evidence.blockNumber) === void 0 || !evidence.logIndexes?.length))
|
|
8402
8939
|
throw incomplete("PAYOUT_RECEIPT_UNVERIFIED", id);
|
|
8403
|
-
if (options.requested === void 0 && payout.result.completion === "unverified")
|
|
8404
|
-
|
|
8940
|
+
if (options.requested === void 0 && payout.result.completion === "unverified") {
|
|
8941
|
+
if (!options.verifyCompletion || id === "yieldseeker" && !evidence.maxCompletion)
|
|
8942
|
+
throw incomplete("MAX_COMPLETION_UNVERIFIED", id);
|
|
8943
|
+
let proof = { status: "unavailable" };
|
|
8944
|
+
try {
|
|
8945
|
+
proof = await bounded(
|
|
8946
|
+
() => options.verifyCompletion(evidence),
|
|
8947
|
+
Math.min(1e4, deadline - Date.now()),
|
|
8948
|
+
() => incomplete("MAX_COMPLETION_UNVERIFIED", id)
|
|
8949
|
+
);
|
|
8950
|
+
} catch {
|
|
8951
|
+
assertActive();
|
|
8952
|
+
}
|
|
8953
|
+
assertActive();
|
|
8954
|
+
evidence.maxCompletionCheck = proof;
|
|
8955
|
+
options.onProgress(evidence);
|
|
8956
|
+
if (proof.status === "remaining") throw incomplete("MAX_REMAINDER_VERIFIED", id);
|
|
8957
|
+
if (proof.status !== "complete") {
|
|
8958
|
+
await pause();
|
|
8959
|
+
continue;
|
|
8960
|
+
}
|
|
8961
|
+
payout.result.completion = void 0;
|
|
8962
|
+
payout.result.type = "full";
|
|
8963
|
+
}
|
|
8405
8964
|
const intended = rawAmount(evidence.intendedAmount);
|
|
8406
8965
|
if (intended !== void 0 && payout.amount < intended && verifiedFee(payout) !== intended - payout.amount)
|
|
8407
8966
|
throw incomplete("PAYOUT_SHORTFALL", id);
|
|
@@ -8411,24 +8970,24 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8411
8970
|
let next = evidence;
|
|
8412
8971
|
if (!next.txHash && (next.userOpHash || next.callsId)) {
|
|
8413
8972
|
next = await options.resolve(next);
|
|
8414
|
-
|
|
8973
|
+
record2(id, payout, next);
|
|
8415
8974
|
}
|
|
8416
8975
|
if (next.transfer === "failed" || next.delivery === "failed") continue;
|
|
8417
8976
|
if (next.txHash) {
|
|
8418
8977
|
next = await options.inspect(next, false);
|
|
8419
|
-
|
|
8978
|
+
record2(id, payout, next);
|
|
8420
8979
|
if (id === "zyfai" && next.transfer === "unknown" && rawAmount(next.blockNumber) !== void 0 && next.logIndexes?.length === 0 && next.transferLogs?.length === 0 && !next.userOpHash && !next.callsId) {
|
|
8421
8980
|
const found = await findPayout(id, next);
|
|
8422
8981
|
if (found) {
|
|
8423
8982
|
next = found;
|
|
8424
|
-
|
|
8983
|
+
record2(id, payout, next);
|
|
8425
8984
|
}
|
|
8426
8985
|
}
|
|
8427
8986
|
} else if (!next.userOpHash && !next.callsId) {
|
|
8428
8987
|
const found = await findPayout(id, next);
|
|
8429
8988
|
if (found) {
|
|
8430
8989
|
next = found;
|
|
8431
|
-
|
|
8990
|
+
record2(id, payout, next);
|
|
8432
8991
|
}
|
|
8433
8992
|
}
|
|
8434
8993
|
if (next.transfer === "confirmed" && next.delivery === "confirmed" || next.transfer === "failed" || next.delivery === "failed") continue;
|
|
@@ -8472,10 +9031,10 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
8472
9031
|
};
|
|
8473
9032
|
try {
|
|
8474
9033
|
return await Promise.race([read2(), new Promise((_, reject) => {
|
|
8475
|
-
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")),
|
|
9034
|
+
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
|
|
8476
9035
|
})]);
|
|
8477
9036
|
} finally {
|
|
8478
|
-
|
|
9037
|
+
active2 = false;
|
|
8479
9038
|
if (timer) clearTimeout(timer);
|
|
8480
9039
|
}
|
|
8481
9040
|
}
|
|
@@ -8493,8 +9052,8 @@ function verifiedFee(payout) {
|
|
|
8493
9052
|
}
|
|
8494
9053
|
|
|
8495
9054
|
// src/lib/swap/swap.permit.ts
|
|
8496
|
-
var
|
|
8497
|
-
var PERMIT_ABI = (0,
|
|
9055
|
+
var import_viem17 = require("viem");
|
|
9056
|
+
var PERMIT_ABI = (0, import_viem17.parseAbi)([
|
|
8498
9057
|
"function nonces(address owner) view returns (uint256)",
|
|
8499
9058
|
"function name() view returns (string)",
|
|
8500
9059
|
"function version() view returns (string)",
|
|
@@ -8521,7 +9080,7 @@ async function tryRead(read2) {
|
|
|
8521
9080
|
function matchDomain(name, versions, chainId, token, onChainSeparator) {
|
|
8522
9081
|
for (const version of versions) {
|
|
8523
9082
|
const domain = { name, version, chainId, verifyingContract: token };
|
|
8524
|
-
if ((0,
|
|
9083
|
+
if ((0, import_viem17.domainSeparator)({ domain }) === onChainSeparator) return domain;
|
|
8525
9084
|
}
|
|
8526
9085
|
return null;
|
|
8527
9086
|
}
|
|
@@ -8574,9 +9133,9 @@ async function buildErc2612Permit(reads, input) {
|
|
|
8574
9133
|
primaryType: "Permit",
|
|
8575
9134
|
message: { owner, spender, value, nonce, deadline }
|
|
8576
9135
|
});
|
|
8577
|
-
const { r, s, v, yParity } = (0,
|
|
9136
|
+
const { r, s, v, yParity } = (0, import_viem17.parseSignature)(signature);
|
|
8578
9137
|
const recoveryV = v ?? BigInt(yParity + 27);
|
|
8579
|
-
return (0,
|
|
9138
|
+
return (0, import_viem17.encodeAbiParameters)(
|
|
8580
9139
|
[
|
|
8581
9140
|
{ type: "address" },
|
|
8582
9141
|
{ type: "address" },
|
|
@@ -8592,7 +9151,7 @@ async function buildErc2612Permit(reads, input) {
|
|
|
8592
9151
|
|
|
8593
9152
|
// src/lib/swap/swap.withdraw-swap.ts
|
|
8594
9153
|
var KEY_PREFIX5 = "owney.swap.withdraw";
|
|
8595
|
-
var
|
|
9154
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
8596
9155
|
var storage5 = () => {
|
|
8597
9156
|
if (typeof window === "undefined") return null;
|
|
8598
9157
|
try {
|
|
@@ -8617,15 +9176,19 @@ var notify = () => {
|
|
|
8617
9176
|
}
|
|
8618
9177
|
}
|
|
8619
9178
|
};
|
|
8620
|
-
function saveWithdrawSwap(
|
|
9179
|
+
function saveWithdrawSwap(record2) {
|
|
8621
9180
|
const store = storage5();
|
|
8622
|
-
if (!store) return;
|
|
9181
|
+
if (!store) return false;
|
|
8623
9182
|
try {
|
|
8624
|
-
store.
|
|
9183
|
+
const current = store.getItem(keyFor3(record2.orderHash));
|
|
9184
|
+
const value = JSON.stringify({ ...current ? JSON.parse(current) : {}, ...record2 });
|
|
9185
|
+
store.setItem(keyFor3(record2.orderHash), value);
|
|
9186
|
+
if (store.getItem(keyFor3(record2.orderHash)) !== value) return false;
|
|
8625
9187
|
} catch {
|
|
8626
|
-
return;
|
|
9188
|
+
return false;
|
|
8627
9189
|
}
|
|
8628
9190
|
notify();
|
|
9191
|
+
return true;
|
|
8629
9192
|
}
|
|
8630
9193
|
function updateWithdrawSwap(orderHash, patch) {
|
|
8631
9194
|
const store = storage5();
|
|
@@ -8648,13 +9211,17 @@ function clearWithdrawSwap(orderHash) {
|
|
|
8648
9211
|
const store = storage5();
|
|
8649
9212
|
if (!store) return;
|
|
8650
9213
|
try {
|
|
8651
|
-
store.
|
|
9214
|
+
const raw2 = store.getItem(keyFor3(orderHash));
|
|
9215
|
+
const record2 = raw2 ? JSON.parse(raw2) : void 0;
|
|
9216
|
+
if (record2 && !["landed", "refunded", "not-swapped"].includes(record2.status))
|
|
9217
|
+
store.setItem(keyFor3(orderHash), JSON.stringify({ ...record2, dismissed: true }));
|
|
9218
|
+
else store.removeItem(keyFor3(orderHash));
|
|
8652
9219
|
} catch {
|
|
8653
9220
|
return;
|
|
8654
9221
|
}
|
|
8655
9222
|
notify();
|
|
8656
9223
|
}
|
|
8657
|
-
function listWithdrawSwaps(now = Date.now()) {
|
|
9224
|
+
function listWithdrawSwaps(now = Date.now(), includeDismissed = false) {
|
|
8658
9225
|
const store = storage5();
|
|
8659
9226
|
if (!store) return [];
|
|
8660
9227
|
const out = [];
|
|
@@ -8669,11 +9236,11 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8669
9236
|
if (!raw2) continue;
|
|
8670
9237
|
try {
|
|
8671
9238
|
const parsed = JSON.parse(raw2);
|
|
8672
|
-
if (now - parsed.createdAt >
|
|
9239
|
+
if (["landed", "refunded", "not-swapped"].includes(parsed.status) && now - (parsed.settledAt ?? parsed.createdAt) > MAX_AGE_MS) {
|
|
8673
9240
|
store.removeItem(key2);
|
|
8674
9241
|
continue;
|
|
8675
9242
|
}
|
|
8676
|
-
out.push(parsed);
|
|
9243
|
+
if (includeDismissed || !parsed.dismissed) out.push(parsed);
|
|
8677
9244
|
} catch {
|
|
8678
9245
|
store.removeItem(key2);
|
|
8679
9246
|
}
|
|
@@ -8683,10 +9250,15 @@ function listWithdrawSwaps(now = Date.now()) {
|
|
|
8683
9250
|
}
|
|
8684
9251
|
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
8685
9252
|
}
|
|
9253
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
9254
|
+
window.addEventListener("storage", (event) => {
|
|
9255
|
+
if (event.key === null || event.key.startsWith(`${KEY_PREFIX5}.`)) notify();
|
|
9256
|
+
});
|
|
9257
|
+
}
|
|
8686
9258
|
|
|
8687
9259
|
// src/lib/swap/swap.pending-deposit.ts
|
|
8688
9260
|
var KEY_PREFIX6 = "owney.swap.pending-deposit";
|
|
8689
|
-
var
|
|
9261
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
8690
9262
|
var storage6 = () => {
|
|
8691
9263
|
if (typeof window === "undefined") return null;
|
|
8692
9264
|
try {
|
|
@@ -8729,7 +9301,7 @@ function listPendingDeposits(now = Date.now()) {
|
|
|
8729
9301
|
if (!raw2) continue;
|
|
8730
9302
|
try {
|
|
8731
9303
|
const parsed = JSON.parse(raw2);
|
|
8732
|
-
if (now - parsed.createdAt >
|
|
9304
|
+
if (now - parsed.createdAt > MAX_AGE_MS2) {
|
|
8733
9305
|
store.removeItem(key2);
|
|
8734
9306
|
continue;
|
|
8735
9307
|
}
|
|
@@ -8778,7 +9350,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
8778
9350
|
}
|
|
8779
9351
|
|
|
8780
9352
|
// src/lib/helpers/withdraw-helper.ts
|
|
8781
|
-
var
|
|
9353
|
+
var import_viem18 = require("viem");
|
|
8782
9354
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
8783
9355
|
return agents.map((agent) => {
|
|
8784
9356
|
let invalidBalance = false;
|
|
@@ -8788,7 +9360,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
8788
9360
|
return 0n;
|
|
8789
9361
|
}
|
|
8790
9362
|
const [whole, fraction] = value.split(".");
|
|
8791
|
-
return raw2 ? BigInt(value) : (0,
|
|
9363
|
+
return raw2 ? BigInt(value) : (0, import_viem18.parseUnits)(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
|
|
8792
9364
|
};
|
|
8793
9365
|
const agentBalance = aggregated[agent.id];
|
|
8794
9366
|
const tokenBalance = agentBalance?.tokens.find(
|
|
@@ -8988,11 +9560,11 @@ function aggregateDailyEarnings(series, tokenSymbol) {
|
|
|
8988
9560
|
const byAsset = /* @__PURE__ */ new Map();
|
|
8989
9561
|
for (const account of series) {
|
|
8990
9562
|
for (const { asset, points } of account.assets) {
|
|
8991
|
-
const
|
|
8992
|
-
if (!points.length || wanted &&
|
|
8993
|
-
const components = byAsset.get(
|
|
9563
|
+
const symbol2 = asset.toUpperCase();
|
|
9564
|
+
if (!points.length || wanted && symbol2 !== wanted) continue;
|
|
9565
|
+
const components = byAsset.get(symbol2) ?? [];
|
|
8994
9566
|
components.push([...points].sort((a, b) => a.date.localeCompare(b.date)));
|
|
8995
|
-
byAsset.set(
|
|
9567
|
+
byAsset.set(symbol2, components);
|
|
8996
9568
|
}
|
|
8997
9569
|
}
|
|
8998
9570
|
return [...byAsset.entries()].map(([asset, components]) => {
|
|
@@ -9025,111 +9597,6 @@ var import_chains6 = require("viem/chains");
|
|
|
9025
9597
|
|
|
9026
9598
|
// src/lib/sponsored-token-batch.ts
|
|
9027
9599
|
var import_viem19 = require("viem");
|
|
9028
|
-
|
|
9029
|
-
// src/lib/permit2-batch.ts
|
|
9030
|
-
var import_viem18 = require("viem");
|
|
9031
|
-
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
9032
|
-
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
9033
|
-
var ERC2612_PERMIT_TYPES = {
|
|
9034
|
-
Permit: [
|
|
9035
|
-
{ name: "owner", type: "address" },
|
|
9036
|
-
{ name: "spender", type: "address" },
|
|
9037
|
-
{ name: "value", type: "uint256" },
|
|
9038
|
-
{ name: "nonce", type: "uint256" },
|
|
9039
|
-
{ name: "deadline", type: "uint256" }
|
|
9040
|
-
]
|
|
9041
|
-
};
|
|
9042
|
-
var ERC2612_READ_ABI = (0, import_viem18.parseAbi)([
|
|
9043
|
-
"function name() view returns (string)",
|
|
9044
|
-
"function version() view returns (string)",
|
|
9045
|
-
"function nonces(address owner) view returns (uint256)"
|
|
9046
|
-
]);
|
|
9047
|
-
function erc2612TypedData(input) {
|
|
9048
|
-
return {
|
|
9049
|
-
domain: {
|
|
9050
|
-
name: input.permit.tokenName,
|
|
9051
|
-
version: input.permit.tokenVersion,
|
|
9052
|
-
chainId: input.chainId,
|
|
9053
|
-
verifyingContract: input.token
|
|
9054
|
-
},
|
|
9055
|
-
types: ERC2612_PERMIT_TYPES,
|
|
9056
|
-
primaryType: "Permit",
|
|
9057
|
-
message: {
|
|
9058
|
-
owner: input.owner,
|
|
9059
|
-
spender: BATCH_PERMIT2_ADDRESS,
|
|
9060
|
-
value: BigInt(input.permit.value),
|
|
9061
|
-
nonce: BigInt(input.permit.nonce),
|
|
9062
|
-
deadline: BigInt(input.permit.deadline)
|
|
9063
|
-
}
|
|
9064
|
-
};
|
|
9065
|
-
}
|
|
9066
|
-
var PERMIT_BATCH_TYPES = {
|
|
9067
|
-
PermitBatchWitnessTransferFrom: [
|
|
9068
|
-
{ name: "permitted", type: "TokenPermissions[]" },
|
|
9069
|
-
{ name: "spender", type: "address" },
|
|
9070
|
-
{ name: "nonce", type: "uint256" },
|
|
9071
|
-
{ name: "deadline", type: "uint256" },
|
|
9072
|
-
{ name: "witness", type: "Deposit" }
|
|
9073
|
-
],
|
|
9074
|
-
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
9075
|
-
TokenPermissions: [
|
|
9076
|
-
{ name: "token", type: "address" },
|
|
9077
|
-
{ name: "amount", type: "uint256" }
|
|
9078
|
-
]
|
|
9079
|
-
};
|
|
9080
|
-
var PERMIT2_BATCH_ABI = (0, import_viem18.parseAbi)([
|
|
9081
|
-
"struct TokenPermissions { address token; uint256 amount; }",
|
|
9082
|
-
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
9083
|
-
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
9084
|
-
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
9085
|
-
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
9086
|
-
]);
|
|
9087
|
-
function batchPermit(b) {
|
|
9088
|
-
return {
|
|
9089
|
-
permitted: b.transfers.map((t) => ({
|
|
9090
|
-
token: b.token,
|
|
9091
|
-
amount: BigInt(t.amount)
|
|
9092
|
-
})),
|
|
9093
|
-
nonce: BigInt(b.nonce),
|
|
9094
|
-
deadline: BigInt(b.deadline)
|
|
9095
|
-
};
|
|
9096
|
-
}
|
|
9097
|
-
function batchTypedData(b, spender) {
|
|
9098
|
-
return {
|
|
9099
|
-
domain: {
|
|
9100
|
-
name: "Permit2",
|
|
9101
|
-
chainId: b.chainId,
|
|
9102
|
-
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
9103
|
-
},
|
|
9104
|
-
types: PERMIT_BATCH_TYPES,
|
|
9105
|
-
primaryType: "PermitBatchWitnessTransferFrom",
|
|
9106
|
-
message: {
|
|
9107
|
-
...batchPermit(b),
|
|
9108
|
-
spender,
|
|
9109
|
-
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
9110
|
-
}
|
|
9111
|
-
};
|
|
9112
|
-
}
|
|
9113
|
-
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
9114
|
-
function batchWitness(b) {
|
|
9115
|
-
return (0, import_viem18.hashStruct)({
|
|
9116
|
-
types: PERMIT_BATCH_TYPES,
|
|
9117
|
-
primaryType: "Deposit",
|
|
9118
|
-
data: { recipients: b.transfers.map((t) => t.to) }
|
|
9119
|
-
});
|
|
9120
|
-
}
|
|
9121
|
-
function batchArgs(b) {
|
|
9122
|
-
return [
|
|
9123
|
-
batchPermit(b),
|
|
9124
|
-
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
9125
|
-
b.from,
|
|
9126
|
-
batchWitness(b),
|
|
9127
|
-
BATCH_WITNESS_TYPE,
|
|
9128
|
-
b.signature
|
|
9129
|
-
];
|
|
9130
|
-
}
|
|
9131
|
-
|
|
9132
|
-
// src/lib/sponsored-token-batch.ts
|
|
9133
9600
|
var memory = /* @__PURE__ */ new Map();
|
|
9134
9601
|
var settledRecords = /* @__PURE__ */ new Map();
|
|
9135
9602
|
var expiredRecords = /* @__PURE__ */ new Map();
|
|
@@ -9157,14 +9624,14 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9157
9624
|
let timer;
|
|
9158
9625
|
let expired = false;
|
|
9159
9626
|
const check = async () => {
|
|
9160
|
-
let
|
|
9161
|
-
if (
|
|
9162
|
-
const current = JSON.parse(
|
|
9627
|
+
let active2 = read(key2);
|
|
9628
|
+
if (active2 && i.expectedTxHash) {
|
|
9629
|
+
const current = JSON.parse(active2);
|
|
9163
9630
|
if (!current.serializedTransaction || (0, import_viem19.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
|
|
9164
|
-
|
|
9631
|
+
active2 = void 0;
|
|
9165
9632
|
}
|
|
9166
9633
|
const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
|
|
9167
|
-
const saved =
|
|
9634
|
+
const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
|
|
9168
9635
|
if (!saved) return;
|
|
9169
9636
|
const body = JSON.parse(saved);
|
|
9170
9637
|
if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem19.isAddressEqual)(body.from, i.owner) || !(0, import_viem19.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
|
|
@@ -9188,8 +9655,8 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
9188
9655
|
args: [i.owner, nonce >> 8n],
|
|
9189
9656
|
blockNumber: block.number
|
|
9190
9657
|
});
|
|
9191
|
-
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n ||
|
|
9192
|
-
if (
|
|
9658
|
+
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
|
|
9659
|
+
if (active2) {
|
|
9193
9660
|
const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
|
|
9194
9661
|
expiredRecords.set(terminalKey, saved);
|
|
9195
9662
|
let persisted = typeof window === "undefined";
|
|
@@ -9247,15 +9714,15 @@ function clear(key2, body) {
|
|
|
9247
9714
|
function sponsorTokenBatch(i) {
|
|
9248
9715
|
const key2 = pendingKey(i);
|
|
9249
9716
|
const plan = planOf(i.transfers);
|
|
9250
|
-
const
|
|
9251
|
-
if (
|
|
9252
|
-
if (
|
|
9717
|
+
const active2 = inflight.get(key2);
|
|
9718
|
+
if (active2) {
|
|
9719
|
+
if (active2.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
|
|
9253
9720
|
return Promise.reject(
|
|
9254
9721
|
new Error(
|
|
9255
9722
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
9256
9723
|
)
|
|
9257
9724
|
);
|
|
9258
|
-
return
|
|
9725
|
+
return active2.promise;
|
|
9259
9726
|
}
|
|
9260
9727
|
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
9261
9728
|
inflight.set(key2, { plan, promise });
|
|
@@ -9409,7 +9876,7 @@ async function execute(i, key2, plan) {
|
|
|
9409
9876
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
9410
9877
|
"Insufficient token balance for this deposit."
|
|
9411
9878
|
);
|
|
9412
|
-
if (allowance < total && (i.chainId
|
|
9879
|
+
if (allowance < total && !supportsSponsoredErc2612(i.chainId, i.token))
|
|
9413
9880
|
throw new OwneyError(
|
|
9414
9881
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
9415
9882
|
"token deposits need a one-time Permit2 approval."
|
|
@@ -9445,7 +9912,7 @@ async function execute(i, key2, plan) {
|
|
|
9445
9912
|
const signature2 = await i.wallet.signTypedData({
|
|
9446
9913
|
account: i.owner,
|
|
9447
9914
|
...erc2612TypedData({
|
|
9448
|
-
chainId:
|
|
9915
|
+
chainId: i.chainId,
|
|
9449
9916
|
token: i.token,
|
|
9450
9917
|
owner: i.owner,
|
|
9451
9918
|
permit: unsignedPermit
|
|
@@ -10073,12 +10540,12 @@ var OwneySDK = class {
|
|
|
10073
10540
|
return [...this.getActiveAgents().keys()].sort();
|
|
10074
10541
|
}
|
|
10075
10542
|
getActiveAgents() {
|
|
10076
|
-
const
|
|
10543
|
+
const active2 = /* @__PURE__ */ new Map();
|
|
10077
10544
|
for (const id of this.activeAgents) {
|
|
10078
10545
|
const agent = this.agents.get(id);
|
|
10079
|
-
if (agent)
|
|
10546
|
+
if (agent) active2.set(id, agent);
|
|
10080
10547
|
}
|
|
10081
|
-
return
|
|
10548
|
+
return active2;
|
|
10082
10549
|
}
|
|
10083
10550
|
/**
|
|
10084
10551
|
* Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
|
|
@@ -10661,10 +11128,10 @@ var OwneySDK = class {
|
|
|
10661
11128
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
10662
11129
|
* two independent auto-recovery mechanisms:
|
|
10663
11130
|
*
|
|
10664
|
-
* 1. Missing Permit2 allowance outside the
|
|
11131
|
+
* 1. Missing Permit2 allowance outside the sponsored ERC-2612 path: when the
|
|
10665
11132
|
* app did not supply its own callback and the attempt fails with
|
|
10666
11133
|
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
10667
|
-
* for that token. Base USDC
|
|
11134
|
+
* for that token. Native Base/Arbitrum USDC uses gasless ERC-2612 approval in
|
|
10668
11135
|
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
10669
11136
|
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
10670
11137
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
@@ -11171,8 +11638,8 @@ var OwneySDK = class {
|
|
|
11171
11638
|
async ensureSwapChain(chainId) {
|
|
11172
11639
|
const provider = this.requireConnectedProvider();
|
|
11173
11640
|
const state = this.requireState();
|
|
11174
|
-
const
|
|
11175
|
-
if (!
|
|
11641
|
+
const chain2 = swapChain(chainId);
|
|
11642
|
+
if (!chain2) {
|
|
11176
11643
|
throw new OwneyError(
|
|
11177
11644
|
"CHAIN_UNSUPPORTED",
|
|
11178
11645
|
`Chain ${chainId} is not supported`,
|
|
@@ -11180,10 +11647,10 @@ var OwneySDK = class {
|
|
|
11180
11647
|
);
|
|
11181
11648
|
}
|
|
11182
11649
|
await ensureWalletOnChain(
|
|
11183
|
-
(0, import_viem21.createPublicClient)({ chain, transport: (0, import_viem21.custom)(provider) }),
|
|
11650
|
+
(0, import_viem21.createPublicClient)({ chain: chain2, transport: (0, import_viem21.custom)(provider) }),
|
|
11184
11651
|
(0, import_viem21.createWalletClient)({
|
|
11185
11652
|
account: state.walletAddress,
|
|
11186
|
-
chain,
|
|
11653
|
+
chain: chain2,
|
|
11187
11654
|
transport: (0, import_viem21.custom)(provider)
|
|
11188
11655
|
}),
|
|
11189
11656
|
chainId
|
|
@@ -11207,15 +11674,15 @@ var OwneySDK = class {
|
|
|
11207
11674
|
async wrapNativeSource(quote, walletAddress, onStage, direction) {
|
|
11208
11675
|
const wrapper = requireWrapper(quote);
|
|
11209
11676
|
const chainId = quote.src.chainId;
|
|
11210
|
-
const
|
|
11677
|
+
const chain2 = swapChain(chainId);
|
|
11211
11678
|
const provider = this.requireConnectedProvider();
|
|
11212
11679
|
const publicClient = (0, import_viem21.createPublicClient)({
|
|
11213
|
-
chain,
|
|
11680
|
+
chain: chain2,
|
|
11214
11681
|
transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
|
|
11215
11682
|
});
|
|
11216
11683
|
const wallet = (0, import_viem21.createWalletClient)({
|
|
11217
11684
|
account: walletAddress,
|
|
11218
|
-
chain,
|
|
11685
|
+
chain: chain2,
|
|
11219
11686
|
transport: (0, import_viem21.custom)(provider)
|
|
11220
11687
|
});
|
|
11221
11688
|
onStage?.("wrapping");
|
|
@@ -11242,7 +11709,7 @@ var OwneySDK = class {
|
|
|
11242
11709
|
data: tx.data,
|
|
11243
11710
|
value: BigInt(tx.value),
|
|
11244
11711
|
account: walletAddress,
|
|
11245
|
-
chain
|
|
11712
|
+
chain: chain2
|
|
11246
11713
|
});
|
|
11247
11714
|
onSubmitted?.(hash2);
|
|
11248
11715
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
@@ -11312,15 +11779,27 @@ var OwneySDK = class {
|
|
|
11312
11779
|
* Kept as a builder rather than baked into the executor so the whole swap
|
|
11313
11780
|
* flow stays testable without a provider — the executor never imports viem.
|
|
11314
11781
|
*/
|
|
11315
|
-
buildSwapDeps(quote) {
|
|
11782
|
+
buildSwapDeps(quote, assertOperation, onTransactionHash) {
|
|
11316
11783
|
const state = this.requireState();
|
|
11317
11784
|
const provider = this.requireConnectedProvider();
|
|
11785
|
+
const revision = this.connectionRevision;
|
|
11786
|
+
const assertWallet = () => {
|
|
11787
|
+
assertOperation?.();
|
|
11788
|
+
if (this.state !== state || this.connectionRevision !== revision)
|
|
11789
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed before swap authorization.");
|
|
11790
|
+
};
|
|
11318
11791
|
const srcChain = swapChain(quote.src.chainId);
|
|
11319
11792
|
const dstChain = swapChain(quote.dst.chainId);
|
|
11320
11793
|
const wallet = (0, import_viem21.createWalletClient)({
|
|
11321
11794
|
account: state.walletAddress,
|
|
11322
11795
|
chain: srcChain,
|
|
11323
|
-
|
|
11796
|
+
// viem can await chain/account preparation before dispatching a wallet
|
|
11797
|
+
// request. Recheck at the transport boundary so an expired attempt or
|
|
11798
|
+
// replaced session cannot send after that internal await completes.
|
|
11799
|
+
transport: (0, import_viem21.custom)({ request: (args) => {
|
|
11800
|
+
assertWallet();
|
|
11801
|
+
return provider.request(args);
|
|
11802
|
+
} }, { retryCount: 0 })
|
|
11324
11803
|
});
|
|
11325
11804
|
const srcPublic = (0, import_viem21.createPublicClient)({
|
|
11326
11805
|
chain: srcChain,
|
|
@@ -11349,6 +11828,7 @@ var OwneySDK = class {
|
|
|
11349
11828
|
});
|
|
11350
11829
|
},
|
|
11351
11830
|
sendTransaction: async (tx) => {
|
|
11831
|
+
assertWallet();
|
|
11352
11832
|
const hash2 = await wallet.sendTransaction({
|
|
11353
11833
|
to: tx.to,
|
|
11354
11834
|
data: tx.data,
|
|
@@ -11356,24 +11836,34 @@ var OwneySDK = class {
|
|
|
11356
11836
|
account: state.walletAddress,
|
|
11357
11837
|
chain: srcChain
|
|
11358
11838
|
});
|
|
11839
|
+
onTransactionHash?.(hash2);
|
|
11359
11840
|
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11360
11841
|
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
11361
11842
|
hash: hash2,
|
|
11362
11843
|
confirmations: 1
|
|
11844
|
+
}).catch(() => {
|
|
11845
|
+
throw new OwneyError(
|
|
11846
|
+
"SWAP_REQUEST_FAILED",
|
|
11847
|
+
"Swap receipt is not confirmed yet.",
|
|
11848
|
+
{ txHash: hash2, settlementUnconfirmed: true, outcome: "submitted-unknown" }
|
|
11849
|
+
);
|
|
11363
11850
|
});
|
|
11364
11851
|
if (receipt.status !== "success") {
|
|
11365
11852
|
throw new OwneyError(
|
|
11366
11853
|
"SWAP_REQUEST_FAILED",
|
|
11367
11854
|
`Swap transaction reverted (tx ${hash2})`,
|
|
11368
|
-
{
|
|
11855
|
+
{ txHash: hash2, outcome: "not-submitted", executionFailed: true }
|
|
11369
11856
|
);
|
|
11370
11857
|
}
|
|
11371
11858
|
return hash2;
|
|
11372
11859
|
},
|
|
11373
|
-
signTypedData: (typedData) =>
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11860
|
+
signTypedData: (typedData) => {
|
|
11861
|
+
assertWallet();
|
|
11862
|
+
return wallet.signTypedData({
|
|
11863
|
+
account: state.walletAddress,
|
|
11864
|
+
...typedData
|
|
11865
|
+
});
|
|
11866
|
+
},
|
|
11377
11867
|
// Chain-bound like every other read here: the wallet provider's chain is
|
|
11378
11868
|
// not ours to rely on mid-swap.
|
|
11379
11869
|
readSourceBalance: async () => {
|
|
@@ -11395,6 +11885,7 @@ var OwneySDK = class {
|
|
|
11395
11885
|
args: [state.walletAddress, spender]
|
|
11396
11886
|
}),
|
|
11397
11887
|
approve: async (spender, amount) => {
|
|
11888
|
+
assertWallet();
|
|
11398
11889
|
const hash2 = await wallet.writeContract({
|
|
11399
11890
|
address: quote.src.address,
|
|
11400
11891
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -11403,14 +11894,22 @@ var OwneySDK = class {
|
|
|
11403
11894
|
account: state.walletAddress,
|
|
11404
11895
|
chain: srcChain
|
|
11405
11896
|
});
|
|
11406
|
-
await srcPublic.waitForTransactionReceipt({
|
|
11897
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
11407
11898
|
hash: hash2,
|
|
11408
11899
|
confirmations: 1,
|
|
11409
11900
|
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
11410
11901
|
});
|
|
11902
|
+
if (receipt.status !== "success") throw new OwneyError(
|
|
11903
|
+
"SWAP_REQUEST_FAILED",
|
|
11904
|
+
"Swap approval reverted.",
|
|
11905
|
+
{ hash: hash2, phase: "approval", outcome: "not-submitted" }
|
|
11906
|
+
);
|
|
11411
11907
|
return hash2;
|
|
11412
11908
|
},
|
|
11413
|
-
ensureChain: (chainId) =>
|
|
11909
|
+
ensureChain: (chainId) => {
|
|
11910
|
+
assertWallet();
|
|
11911
|
+
return this.ensureSwapChain(chainId);
|
|
11912
|
+
},
|
|
11414
11913
|
/**
|
|
11415
11914
|
* Sign the approval instead of paying for it. Null means the token
|
|
11416
11915
|
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
@@ -11430,10 +11929,13 @@ var OwneySDK = class {
|
|
|
11430
11929
|
spender,
|
|
11431
11930
|
value: amount,
|
|
11432
11931
|
chainId: quote.src.chainId,
|
|
11433
|
-
signTypedData: (payload) =>
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11932
|
+
signTypedData: (payload) => {
|
|
11933
|
+
assertWallet();
|
|
11934
|
+
return wallet.signTypedData({
|
|
11935
|
+
account: state.walletAddress,
|
|
11936
|
+
...payload
|
|
11937
|
+
});
|
|
11938
|
+
}
|
|
11437
11939
|
}
|
|
11438
11940
|
),
|
|
11439
11941
|
/**
|
|
@@ -11483,36 +11985,20 @@ var OwneySDK = class {
|
|
|
11483
11985
|
);
|
|
11484
11986
|
}
|
|
11485
11987
|
/**
|
|
11486
|
-
*
|
|
11487
|
-
*
|
|
11488
|
-
*
|
|
11489
|
-
*
|
|
11490
|
-
*
|
|
11491
|
-
*
|
|
11492
|
-
* Something does. A first WETH deposit needs a one-time Permit2 approval,
|
|
11493
|
-
* and that approval is a transaction. A wallet holding no native gas token -
|
|
11494
|
-
* the normal state of someone who just swapped everything INTO WETH - pays
|
|
11495
|
-
* for it out of the WETH. The deposit then asked for ~2e12 wei more than was
|
|
11496
|
-
* left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
|
|
11497
|
-
* the funds were safe but unadded (ROUT-242).
|
|
11498
|
-
*
|
|
11499
|
-
* So the approval happens here, before the amount is settled, and the amount
|
|
11500
|
-
* comes from a read taken after it. Two things this deliberately is not:
|
|
11501
|
-
*
|
|
11502
|
-
* - It is not a balance sweep. `received` is a DELTA over the pre-swap
|
|
11503
|
-
* balance, so a wallet already holding WETH reads far above it; depositing
|
|
11504
|
-
* the balance would move funds the user never offered. The arrival is the
|
|
11505
|
-
* ceiling and the balance is only ever allowed to lower it.
|
|
11506
|
-
* - It does not touch USDC, which moves on signed typed data and so spends
|
|
11507
|
-
* nothing of the user's in between, nor a paymaster-configured client,
|
|
11508
|
-
* where the sponsor covers the transfer and Permit2 never applies.
|
|
11988
|
+
* Finish any user-paid Permit2 approval before measuring the deposit.
|
|
11989
|
+
* Wallets can charge approval gas in the received token (ROUT-242/544).
|
|
11990
|
+
* Cap the result at the swap arrival to preserve pre-existing wallet funds.
|
|
11991
|
+
* Native Base/Arbitrum USDC uses sponsored ERC-2612; configured paymaster
|
|
11992
|
+
* wallet calls also cover approval without spending the received tokens.
|
|
11509
11993
|
*/
|
|
11510
|
-
async settleSwapDepositAmount(chainId, token,
|
|
11511
|
-
if (
|
|
11994
|
+
async settleSwapDepositAmount(chainId, token, symbol2, received) {
|
|
11995
|
+
if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
|
|
11512
11996
|
return received.toString();
|
|
11513
11997
|
}
|
|
11514
11998
|
const allowance = await this.readTokenPermit2Allowance(chainId, token);
|
|
11515
|
-
if (allowance < received)
|
|
11999
|
+
if (allowance < received) {
|
|
12000
|
+
await this.approvePermit2(symbol2, received, chainId);
|
|
12001
|
+
}
|
|
11516
12002
|
const balance = await this.readTokenBalance(chainId, token);
|
|
11517
12003
|
return (balance < received ? balance : received).toString();
|
|
11518
12004
|
}
|
|
@@ -11639,21 +12125,30 @@ var OwneySDK = class {
|
|
|
11639
12125
|
* Every order is attempted even if an earlier one throws: they are unrelated
|
|
11640
12126
|
* escrows, and one ending badly must not abandon the next one's secrets.
|
|
11641
12127
|
* `unfinished` is not a verdict — the order may still settle, so unless the
|
|
11642
|
-
*
|
|
12128
|
+
* provider has confirmed full settlement or refund, its preimages are kept.
|
|
11643
12129
|
*/
|
|
11644
12130
|
async resumePendingSwaps(options = {}) {
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
12131
|
+
await mapConcurrent(listWithdrawSwaps(Date.now(), true).filter((record2) => record2.txHash && record2.status === "unknown" && (!options.orderHash || options.orderHash === record2.orderHash)), async (record2) => {
|
|
12132
|
+
try {
|
|
12133
|
+
const rpc2 = (0, import_viem21.createPublicClient)({ chain: swapChain(record2.srcChainId), transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls) });
|
|
12134
|
+
const receipt = await bounded(
|
|
12135
|
+
() => rpc2.getTransactionReceipt({ hash: record2.txHash }),
|
|
12136
|
+
8e3,
|
|
12137
|
+
() => new Error("Receipt unavailable")
|
|
12138
|
+
);
|
|
12139
|
+
updateWithdrawSwap(record2.orderHash, { status: receipt.status === "success" ? "landed" : "not-swapped" });
|
|
12140
|
+
} catch {
|
|
12141
|
+
}
|
|
12142
|
+
});
|
|
12143
|
+
const pending = listOrders().filter((order) => !isOrderObserved(order.orderHash) && (!options.orderHash || order.orderHash === options.orderHash));
|
|
11650
12144
|
const runner = this.swapRunnerDeps();
|
|
11651
|
-
const
|
|
11652
|
-
|
|
12145
|
+
const sameChain = this.resumeSameChainSwaps(options);
|
|
12146
|
+
const results = await mapConcurrent(pending, async (order) => {
|
|
11653
12147
|
try {
|
|
11654
12148
|
await runFusionOrder(runner, {
|
|
11655
12149
|
orderHash: order.orderHash,
|
|
11656
12150
|
secrets: order.secrets,
|
|
12151
|
+
hasPartialFill: listWithdrawSwaps(Date.now(), true).some((record2) => record2.orderHash === order.orderHash && record2.status === "partially-filled"),
|
|
11657
12152
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11658
12153
|
...options.onStage ? {
|
|
11659
12154
|
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
@@ -11661,25 +12156,23 @@ var OwneySDK = class {
|
|
|
11661
12156
|
});
|
|
11662
12157
|
clearOrder(order.orderHash);
|
|
11663
12158
|
updateWithdrawSwap(order.orderHash, { status: "landed" });
|
|
11664
|
-
|
|
11665
|
-
orderHash: order.orderHash,
|
|
11666
|
-
status: "completed"
|
|
11667
|
-
});
|
|
12159
|
+
return { orderHash: order.orderHash, status: "completed" };
|
|
11668
12160
|
} catch (error) {
|
|
11669
|
-
if (error instanceof OwneyError &&
|
|
12161
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
11670
12162
|
clearOrder(order.orderHash);
|
|
11671
12163
|
updateWithdrawSwap(order.orderHash, { status: "refunded" });
|
|
11672
12164
|
} else {
|
|
11673
|
-
|
|
12165
|
+
if (!(error instanceof OwneyError && error.details?.watchActive))
|
|
12166
|
+
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" });
|
|
11674
12167
|
}
|
|
11675
|
-
|
|
12168
|
+
return {
|
|
11676
12169
|
orderHash: order.orderHash,
|
|
11677
12170
|
status: "unfinished",
|
|
11678
12171
|
...error instanceof OwneyError ? { error } : {}
|
|
11679
|
-
}
|
|
12172
|
+
};
|
|
11680
12173
|
}
|
|
11681
|
-
}
|
|
11682
|
-
await
|
|
12174
|
+
});
|
|
12175
|
+
await sameChain;
|
|
11683
12176
|
return results;
|
|
11684
12177
|
}
|
|
11685
12178
|
/**
|
|
@@ -11700,34 +12193,32 @@ var OwneySDK = class {
|
|
|
11700
12193
|
* anything else.
|
|
11701
12194
|
*/
|
|
11702
12195
|
async resumeSameChainSwaps(options = {}) {
|
|
11703
|
-
const running = listWithdrawSwaps().filter(
|
|
11704
|
-
|
|
11705
|
-
);
|
|
11706
|
-
for (const record of running) {
|
|
12196
|
+
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));
|
|
12197
|
+
await mapConcurrent(running, async (record2) => {
|
|
11707
12198
|
const runner = {
|
|
11708
12199
|
// The chain bound in. That product keys its orders per chain, and the
|
|
11709
12200
|
// Fusion+ endpoint answers 404 for one — which would read as an order
|
|
11710
12201
|
// that vanished rather than one still in the auction.
|
|
11711
|
-
orderStatus: (h) => this.swapApi().orderStatus(h,
|
|
12202
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, record2.srcChainId),
|
|
11712
12203
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
11713
12204
|
now: () => Date.now()
|
|
11714
12205
|
};
|
|
11715
12206
|
try {
|
|
11716
12207
|
await runSameChainOrder(runner, {
|
|
11717
|
-
orderHash:
|
|
12208
|
+
orderHash: record2.orderHash,
|
|
12209
|
+
hasPartialFill: record2.status === "partially-filled",
|
|
11718
12210
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
11719
12211
|
...options.onStage ? {
|
|
11720
|
-
onStage: (stage) => options.onStage(
|
|
12212
|
+
onStage: (stage) => options.onStage(record2.orderHash, stage)
|
|
11721
12213
|
} : {}
|
|
11722
12214
|
});
|
|
11723
|
-
updateWithdrawSwap(
|
|
12215
|
+
updateWithdrawSwap(record2.orderHash, { status: "landed" });
|
|
11724
12216
|
} catch (error) {
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
});
|
|
12217
|
+
if (error instanceof OwneyError && error.details?.watchActive) return;
|
|
12218
|
+
const outcome = error instanceof OwneyError ? error.details?.outcome : void 0;
|
|
12219
|
+
updateWithdrawSwap(record2.orderHash, { status: outcome === "not-submitted" ? "not-swapped" : outcome === "partially-filled" ? "partially-filled" : "unknown" });
|
|
11729
12220
|
}
|
|
11730
|
-
}
|
|
12221
|
+
});
|
|
11731
12222
|
}
|
|
11732
12223
|
/**
|
|
11733
12224
|
* What the wallet actually holds, across every supported chain, restricted
|
|
@@ -11883,6 +12374,92 @@ var OwneySDK = class {
|
|
|
11883
12374
|
);
|
|
11884
12375
|
}
|
|
11885
12376
|
}
|
|
12377
|
+
/** Read-only confirmation of a saved pre-swap withdrawal. Never withdraws or swaps. */
|
|
12378
|
+
async recheckWithdrawalSwap(id) {
|
|
12379
|
+
const record2 = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
|
|
12380
|
+
const state = this.requireState();
|
|
12381
|
+
if (!record2?.recovery || record2.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || record2.srcChainId !== this.requireChainId() || record2.recovery.phase === "submitted")
|
|
12382
|
+
throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "Reconnect the original wallet and source chain to check this withdrawal.");
|
|
12383
|
+
if (this.withdrawalSwapSubmissions.has(`${state.walletAddress.toLowerCase()}:${record2.srcChainId}:${record2.srcSymbol}`))
|
|
12384
|
+
throw new OwneyError("WITHDRAW_IN_PROGRESS", "This withdrawal is still being observed by its original attempt.");
|
|
12385
|
+
const revision = this.connectionRevision;
|
|
12386
|
+
const asset = SupportedAssets.find((a) => a.chainId === record2.srcChainId && a.symbol === record2.srcSymbol);
|
|
12387
|
+
if (!asset || !record2.recovery.results)
|
|
12388
|
+
throw new OwneyError(
|
|
12389
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12390
|
+
"Some withdrawal results remain unknown. Check Activity and balances before continuing.",
|
|
12391
|
+
{ reason: "WITHDRAWAL_RESULTS_UNAVAILABLE", swapSubmitted: false }
|
|
12392
|
+
);
|
|
12393
|
+
const rpc2 = (0, import_viem21.createPublicClient)({
|
|
12394
|
+
chain: VIEM_CHAIN2[record2.srcChainId],
|
|
12395
|
+
transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls)
|
|
12396
|
+
});
|
|
12397
|
+
const recovery = record2.recovery;
|
|
12398
|
+
const assertRecoveryCurrent = () => {
|
|
12399
|
+
if (this.state !== state || this.connectionRevision !== revision)
|
|
12400
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet changed during confirmation.");
|
|
12401
|
+
const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
|
|
12402
|
+
if (!latest?.recovery || latest.recovery.phase === "submitted")
|
|
12403
|
+
throw new OwneyError("WITHDRAW_IN_PROGRESS", "This intent has already advanced. Check its current order.");
|
|
12404
|
+
};
|
|
12405
|
+
const persist = () => {
|
|
12406
|
+
assertRecoveryCurrent();
|
|
12407
|
+
saveWithdrawSwap(record2);
|
|
12408
|
+
};
|
|
12409
|
+
for (const [id2, result] of Object.entries(recovery.results)) {
|
|
12410
|
+
const evidence = recovery.evidence[id2] ?? result.evidence;
|
|
12411
|
+
if (evidence) result.evidence = { ...evidence, transfer: "pending", delivery: "pending" };
|
|
12412
|
+
if (id2 === "yieldseeker" && recovery.requested === void 0) result.completion = "unverified";
|
|
12413
|
+
}
|
|
12414
|
+
try {
|
|
12415
|
+
const arrived = await awaitWithdrawalPayouts({
|
|
12416
|
+
results: recovery.results,
|
|
12417
|
+
requested: recovery.requested === void 0 ? void 0 : BigInt(recovery.requested),
|
|
12418
|
+
walletAddress: state.walletAddress,
|
|
12419
|
+
chainId: record2.srcChainId,
|
|
12420
|
+
token: asset.symbol,
|
|
12421
|
+
submittedAt: recovery.submittedAt,
|
|
12422
|
+
baseline: BigInt(recovery.baseline),
|
|
12423
|
+
baselineBlock: BigInt(recovery.baselineBlock),
|
|
12424
|
+
timeoutMs: 3e4,
|
|
12425
|
+
assertCurrent: assertRecoveryCurrent,
|
|
12426
|
+
readBalance: () => rpc2.readContract({ address: asset.address, abi: import_viem21.erc20Abi, functionName: "balanceOf", args: [state.walletAddress] }),
|
|
12427
|
+
inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
|
|
12428
|
+
resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt: recovery.submittedAt }),
|
|
12429
|
+
history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
|
|
12430
|
+
verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
|
|
12431
|
+
onProgress: (value) => {
|
|
12432
|
+
recovery.evidence[value.agentId] = value;
|
|
12433
|
+
persist();
|
|
12434
|
+
}
|
|
12435
|
+
});
|
|
12436
|
+
record2.withdrawn = arrived.toString();
|
|
12437
|
+
record2.status = "ready";
|
|
12438
|
+
recovery.phase = "ready";
|
|
12439
|
+
recovery.reason = void 0;
|
|
12440
|
+
persist();
|
|
12441
|
+
return record2;
|
|
12442
|
+
} catch (error) {
|
|
12443
|
+
record2.status = "withdrawal-incomplete";
|
|
12444
|
+
recovery.reason = error instanceof OwneyError ? String(error.details?.reason ?? error.code) : "CONFIRMATION_UNAVAILABLE";
|
|
12445
|
+
try {
|
|
12446
|
+
persist();
|
|
12447
|
+
} catch {
|
|
12448
|
+
}
|
|
12449
|
+
throw error;
|
|
12450
|
+
}
|
|
12451
|
+
}
|
|
12452
|
+
/** Explicit foreground continuation: spends verified payouts, never withdraws again. */
|
|
12453
|
+
async continueWithdrawalSwap(id, callbacks = {}) {
|
|
12454
|
+
const record2 = await this.recheckWithdrawalSwap(id);
|
|
12455
|
+
return this.withdrawAndSwap({
|
|
12456
|
+
from: { chainId: record2.srcChainId, symbol: record2.srcSymbol },
|
|
12457
|
+
to: { chainId: record2.dstChainId, symbol: record2.dstSymbol, decimals: record2.dstDecimals },
|
|
12458
|
+
...record2.recovery?.agentId ? { agentId: record2.recovery.agentId } : {},
|
|
12459
|
+
recoveryId: id,
|
|
12460
|
+
...callbacks
|
|
12461
|
+
});
|
|
12462
|
+
}
|
|
11886
12463
|
/**
|
|
11887
12464
|
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
11888
12465
|
* to hold, delivered to their own wallet.
|
|
@@ -11911,7 +12488,14 @@ var OwneySDK = class {
|
|
|
11911
12488
|
const state = this.requireState();
|
|
11912
12489
|
const activeChainId = this.requireChainId();
|
|
11913
12490
|
const revision = this.connectionRevision;
|
|
12491
|
+
let active2 = true;
|
|
12492
|
+
let record2;
|
|
12493
|
+
let hadDurableRecord = false;
|
|
12494
|
+
const startedAt = Date.now();
|
|
12495
|
+
let confirmationDeadline = Infinity;
|
|
11914
12496
|
const assertCurrent = () => {
|
|
12497
|
+
options.assertCurrent?.();
|
|
12498
|
+
if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
|
|
11915
12499
|
if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
|
|
11916
12500
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
|
|
11917
12501
|
};
|
|
@@ -11953,30 +12537,95 @@ var OwneySDK = class {
|
|
|
11953
12537
|
functionName: "balanceOf",
|
|
11954
12538
|
args: [state.walletAddress]
|
|
11955
12539
|
});
|
|
11956
|
-
const
|
|
11957
|
-
|
|
12540
|
+
const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
|
|
12541
|
+
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))
|
|
12542
|
+
throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
|
|
12543
|
+
const prepareTimeout = () => new OwneyError(
|
|
12544
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12545
|
+
"Could not read the source wallet. No new withdrawal was submitted.",
|
|
12546
|
+
{ reason: "BASELINE_UNAVAILABLE", swapSubmitted: false }
|
|
12547
|
+
);
|
|
12548
|
+
const baselineBlock = saved ? BigInt(saved.recovery.baselineBlock) : await bounded(() => srcPublic.getBlockNumber({ cacheTime: 0 }), 2e4, prepareTimeout);
|
|
12549
|
+
const baseline = saved ? BigInt(saved.recovery.baseline) : await bounded(readWalletBalance, 2e4, prepareTimeout);
|
|
11958
12550
|
assertCurrent();
|
|
11959
12551
|
debugLog("owney-sdk", "withdrawAndSwap: baseline", {
|
|
11960
12552
|
asset: `${asset.symbol}@${asset.chainId}`,
|
|
11961
12553
|
baseline: baseline.toString()
|
|
11962
12554
|
});
|
|
11963
12555
|
options.onSwapProgress?.("withdrawing");
|
|
11964
|
-
const submittedAt = Date.now();
|
|
11965
|
-
const requested = options.amount === void 0 ? void 0 : (0, import_viem21.parseUnits)(options.amount, asset.decimals);
|
|
12556
|
+
const submittedAt = saved?.recovery?.submittedAt ?? Date.now();
|
|
12557
|
+
const requested = saved ? saved.recovery.requested === void 0 ? void 0 : BigInt(saved.recovery.requested) : options.amount === void 0 ? void 0 : (0, import_viem21.parseUnits)(options.amount, asset.decimals);
|
|
12558
|
+
confirmationDeadline = Date.now() + (options.arrivalTimeoutMs ?? 18e4);
|
|
12559
|
+
const attemptId = saved?.recovery?.attemptId ?? `withdraw-${globalThis.crypto.randomUUID()}`;
|
|
12560
|
+
record2 = saved ?? {
|
|
12561
|
+
orderHash: attemptId,
|
|
12562
|
+
walletAddress: state.walletAddress,
|
|
12563
|
+
status: "withdrawal-pending",
|
|
12564
|
+
withdrawn: "0",
|
|
12565
|
+
expected: "0",
|
|
12566
|
+
createdAt: Date.now(),
|
|
12567
|
+
srcSymbol: asset.symbol,
|
|
12568
|
+
srcChainId: activeChainId,
|
|
12569
|
+
srcDecimals: asset.decimals,
|
|
12570
|
+
dstSymbol: options.to.symbol,
|
|
12571
|
+
dstChainId: options.to.chainId,
|
|
12572
|
+
dstDecimals: options.to.decimals ?? 18,
|
|
12573
|
+
recovery: {
|
|
12574
|
+
attemptId,
|
|
12575
|
+
phase: "withdrawing",
|
|
12576
|
+
baseline: baseline.toString(),
|
|
12577
|
+
baselineBlock: baselineBlock.toString(),
|
|
12578
|
+
submittedAt,
|
|
12579
|
+
deadline: confirmationDeadline,
|
|
12580
|
+
requested: requested?.toString(),
|
|
12581
|
+
agentId: options.agentId,
|
|
12582
|
+
evidence: {}
|
|
12583
|
+
}
|
|
12584
|
+
};
|
|
12585
|
+
hadDurableRecord = saveWithdrawSwap(record2);
|
|
12586
|
+
const assertSubmission = () => {
|
|
12587
|
+
assertCurrent();
|
|
12588
|
+
if (Date.now() >= confirmationDeadline) throw new OwneyError(
|
|
12589
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12590
|
+
"Withdrawal confirmation timed out. Check Activity before retrying.",
|
|
12591
|
+
{ reason: "PAYOUT_CONFIRMATION_TIMEOUT", swapSubmitted: false }
|
|
12592
|
+
);
|
|
12593
|
+
};
|
|
11966
12594
|
const evidence = /* @__PURE__ */ new Map();
|
|
11967
12595
|
const onProgress = (next) => {
|
|
11968
12596
|
if (next.chainId !== activeChainId || next.token !== asset.symbol || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
|
|
11969
12597
|
evidence.set(next.agentId, next);
|
|
12598
|
+
if (record2?.recovery) {
|
|
12599
|
+
record2.recovery.evidence[next.agentId] = next;
|
|
12600
|
+
if (active2) saveWithdrawSwap(record2);
|
|
12601
|
+
else {
|
|
12602
|
+
const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
|
|
12603
|
+
if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
|
|
12604
|
+
...latest.recovery,
|
|
12605
|
+
evidence: { ...latest.recovery.evidence, [next.agentId]: next }
|
|
12606
|
+
} });
|
|
12607
|
+
}
|
|
12608
|
+
}
|
|
11970
12609
|
notifyFunding(options.onWithdrawalProgress, next);
|
|
11971
12610
|
};
|
|
11972
12611
|
let withdraw;
|
|
11973
12612
|
try {
|
|
11974
|
-
withdraw = await this.withdraw({
|
|
12613
|
+
withdraw = saved ? { agentResult: saved.recovery.results, totalWithdrawn: null } : await bounded(() => this.withdraw({
|
|
11975
12614
|
asset: asset.symbol,
|
|
11976
12615
|
...requested === void 0 ? {} : { amount: requested.toString() },
|
|
11977
12616
|
...options.agentId ? { agentId: options.agentId } : {},
|
|
11978
|
-
onProgress
|
|
11979
|
-
|
|
12617
|
+
onProgress,
|
|
12618
|
+
assertCurrent: assertSubmission,
|
|
12619
|
+
onBeforeSubmission: (next) => {
|
|
12620
|
+
assertSubmission();
|
|
12621
|
+
onProgress(next);
|
|
12622
|
+
},
|
|
12623
|
+
observationDeadline: confirmationDeadline
|
|
12624
|
+
}), Math.max(0, confirmationDeadline - Date.now()), () => new OwneyError(
|
|
12625
|
+
"WITHDRAW_SWAP_INCOMPLETE",
|
|
12626
|
+
"Withdrawal confirmation timed out. Check Activity and balances before trying again.",
|
|
12627
|
+
{ reason: "WITHDRAWAL_DISPATCH_TIMEOUT", swapSubmitted: false, withdrawalEvidence: Object.fromEntries(evidence) }
|
|
12628
|
+
));
|
|
11980
12629
|
} catch (error) {
|
|
11981
12630
|
const details = error instanceof OwneyError ? error.details : void 0;
|
|
11982
12631
|
const moved = [...evidence.values()].some((value) => value.transfer !== "failed" && (value.delivery === "confirmed" || value.transfer === "unknown" || value.txHash || value.userOpHash || value.callsId || value.withdrawalRequest?.attempted));
|
|
@@ -11998,6 +12647,9 @@ var OwneySDK = class {
|
|
|
11998
12647
|
const results = "agentResult" in withdraw ? withdraw.agentResult : {
|
|
11999
12648
|
[options.agentId ?? withdraw.evidence?.agentId ?? "unknown"]: withdraw
|
|
12000
12649
|
};
|
|
12650
|
+
record2.recovery.results = results;
|
|
12651
|
+
record2.recovery.phase = "verifying";
|
|
12652
|
+
saveWithdrawSwap(record2);
|
|
12001
12653
|
const arrived = await awaitWithdrawalPayouts({
|
|
12002
12654
|
results,
|
|
12003
12655
|
requested,
|
|
@@ -12009,7 +12661,8 @@ var OwneySDK = class {
|
|
|
12009
12661
|
baselineBlock,
|
|
12010
12662
|
assertCurrent,
|
|
12011
12663
|
readBalance: readWalletBalance,
|
|
12012
|
-
|
|
12664
|
+
deadline: confirmationDeadline,
|
|
12665
|
+
verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
|
|
12013
12666
|
inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
|
|
12014
12667
|
resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt }),
|
|
12015
12668
|
history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
|
|
@@ -12017,34 +12670,89 @@ var OwneySDK = class {
|
|
|
12017
12670
|
});
|
|
12018
12671
|
assertCurrent();
|
|
12019
12672
|
const withdrawn = arrived.toString();
|
|
12673
|
+
record2.withdrawn = withdrawn;
|
|
12674
|
+
record2.status = "ready";
|
|
12675
|
+
record2.recovery.phase = "ready";
|
|
12676
|
+
hadDurableRecord = saveWithdrawSwap(record2) || hadDurableRecord;
|
|
12020
12677
|
options.onSwapProgress?.("withdrawn");
|
|
12021
12678
|
try {
|
|
12022
|
-
const quote = await this.swapApi().quote({
|
|
12679
|
+
const quote = await bounded(() => this.swapApi().quote({
|
|
12023
12680
|
from: { ...options.from, amount: withdrawn },
|
|
12024
12681
|
to: options.to,
|
|
12025
12682
|
direction: "withdraw",
|
|
12026
12683
|
walletAddress: state.walletAddress
|
|
12027
|
-
});
|
|
12684
|
+
}), 2e4, () => new OwneyError("SWAP_REQUEST_FAILED", "Swap pricing timed out.", { outcome: "not-submitted" }));
|
|
12028
12685
|
assertCurrent();
|
|
12029
|
-
await
|
|
12686
|
+
await bounded(
|
|
12687
|
+
() => this.ensureSwapChain(quote.src.chainId),
|
|
12688
|
+
18e4,
|
|
12689
|
+
() => new OwneyError("SWAP_WALLET_PROMPT_TIMEOUT", "The wallet did not confirm its chain. No swap was submitted.", { outcome: "not-submitted" })
|
|
12690
|
+
);
|
|
12030
12691
|
assertCurrent();
|
|
12031
|
-
const swap = await executeSwap(this.buildSwapDeps(quote
|
|
12692
|
+
const swap = await executeSwap(this.buildSwapDeps(quote, assertCurrent, (hash2) => {
|
|
12693
|
+
record2.txHash = hash2;
|
|
12694
|
+
saveWithdrawSwap(record2);
|
|
12695
|
+
}), {
|
|
12696
|
+
onTransactionRequested: () => {
|
|
12697
|
+
assertCurrent();
|
|
12698
|
+
record2.status = "unknown";
|
|
12699
|
+
record2.recovery.phase = "submitted";
|
|
12700
|
+
if (!saveWithdrawSwap(record2) && hadDurableRecord)
|
|
12701
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
|
|
12702
|
+
},
|
|
12703
|
+
assertCurrent,
|
|
12704
|
+
sourceReserve: baseline,
|
|
12032
12705
|
quote,
|
|
12033
12706
|
walletAddress: state.walletAddress,
|
|
12034
12707
|
direction: "withdraw",
|
|
12035
12708
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
12036
12709
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
|
|
12037
|
-
|
|
12710
|
+
onOrderPrepared: (orderHash, details) => {
|
|
12711
|
+
assertCurrent();
|
|
12712
|
+
const previous = record2.orderHash;
|
|
12713
|
+
record2.orderHash = orderHash;
|
|
12714
|
+
record2.status = "unknown";
|
|
12715
|
+
record2.recovery.phase = "submitted";
|
|
12716
|
+
record2.spent = details.spent;
|
|
12717
|
+
record2.expected = details.expected;
|
|
12718
|
+
if (!saveWithdrawSwap(record2) && hadDurableRecord) {
|
|
12719
|
+
record2.orderHash = previous;
|
|
12720
|
+
throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
|
|
12721
|
+
}
|
|
12722
|
+
if (previous !== orderHash) {
|
|
12723
|
+
updateWithdrawSwap(previous, { status: "not-swapped" });
|
|
12724
|
+
clearWithdrawSwap(previous);
|
|
12725
|
+
}
|
|
12726
|
+
},
|
|
12727
|
+
onOrderSubmitted: (orderHash, details) => {
|
|
12038
12728
|
releaseSubmission();
|
|
12039
|
-
|
|
12729
|
+
record2.orderHash = orderHash;
|
|
12730
|
+
record2.status = "swapping";
|
|
12731
|
+
record2.recovery.phase = "submitted";
|
|
12732
|
+
record2.createdAt = Date.now();
|
|
12733
|
+
const durable = saveWithdrawSwap(record2);
|
|
12734
|
+
options.onOrderSubmitted?.({
|
|
12735
|
+
orderHash,
|
|
12736
|
+
withdrawn,
|
|
12737
|
+
expected: details?.expected ?? quote.dst.amount,
|
|
12738
|
+
spent: details?.spent,
|
|
12739
|
+
durable: durable && details?.durable !== false
|
|
12740
|
+
});
|
|
12040
12741
|
}
|
|
12041
12742
|
});
|
|
12743
|
+
record2.status = "landed";
|
|
12744
|
+
record2.received = swap.received;
|
|
12745
|
+
record2.spent = swap.spent;
|
|
12746
|
+
saveWithdrawSwap(record2);
|
|
12042
12747
|
return { withdraw, withdrawn, swap };
|
|
12043
12748
|
} catch (error) {
|
|
12044
12749
|
throw new OwneyError(
|
|
12045
12750
|
"WITHDRAW_SWAP_FAILED",
|
|
12046
|
-
`
|
|
12751
|
+
`The withdrawal completed, but the swap to ${options.to.symbol} could not be confirmed. Check the order and both balances before retrying.`,
|
|
12047
12752
|
{
|
|
12753
|
+
outcome: record2.recovery?.phase === "ready" ? "not-submitted" : "submitted-unknown",
|
|
12754
|
+
...error instanceof OwneyError ? error.details : {},
|
|
12755
|
+
...record2.orderHash.startsWith("0x") ? { orderHash: record2.orderHash, settlementUnconfirmed: true } : {},
|
|
12048
12756
|
withdrawn,
|
|
12049
12757
|
asset: asset.symbol,
|
|
12050
12758
|
chainId: asset.chainId,
|
|
@@ -12055,7 +12763,25 @@ var OwneySDK = class {
|
|
|
12055
12763
|
}
|
|
12056
12764
|
);
|
|
12057
12765
|
}
|
|
12766
|
+
} catch (error) {
|
|
12767
|
+
if (record2) {
|
|
12768
|
+
if (error instanceof OwneyError && error.details) Object.assign(error.details, {
|
|
12769
|
+
attemptId: record2.recovery?.attemptId,
|
|
12770
|
+
phase: record2.recovery?.phase,
|
|
12771
|
+
elapsedMs: Date.now() - startedAt,
|
|
12772
|
+
txHash: record2.txHash,
|
|
12773
|
+
withdrawalEvidence: record2.recovery?.evidence
|
|
12774
|
+
});
|
|
12775
|
+
const details = error instanceof OwneyError ? error.details : void 0;
|
|
12776
|
+
const outcome = details?.outcome;
|
|
12777
|
+
if (record2.recovery && outcome === "not-submitted") record2.recovery.phase = "ready";
|
|
12778
|
+
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";
|
|
12779
|
+
if (record2.recovery) record2.recovery.reason = typeof details?.reason === "string" ? details.reason : void 0;
|
|
12780
|
+
saveWithdrawSwap(record2);
|
|
12781
|
+
}
|
|
12782
|
+
throw error;
|
|
12058
12783
|
} finally {
|
|
12784
|
+
active2 = false;
|
|
12059
12785
|
releaseSubmission();
|
|
12060
12786
|
}
|
|
12061
12787
|
}
|
|
@@ -12088,7 +12814,15 @@ var OwneySDK = class {
|
|
|
12088
12814
|
const attempted = /* @__PURE__ */ new Set();
|
|
12089
12815
|
this.withdrawalSubmissions.add(submissionKey);
|
|
12090
12816
|
try {
|
|
12091
|
-
return await this.withdrawFromAgents(options, state, chainId, attempted)
|
|
12817
|
+
return options.observationDeadline === void 0 ? await this.withdrawFromAgents(options, state, chainId, attempted) : await bounded(
|
|
12818
|
+
() => this.withdrawFromAgents(options, state, chainId, attempted),
|
|
12819
|
+
Math.max(0, options.observationDeadline - Date.now()),
|
|
12820
|
+
() => new OwneyError(
|
|
12821
|
+
"WITHDRAW_UNCONFIRMED",
|
|
12822
|
+
"Withdrawal dispatch could not be confirmed before the deadline.",
|
|
12823
|
+
{ phase: "observe", reason: "WITHDRAWAL_DISPATCH_TIMEOUT" }
|
|
12824
|
+
)
|
|
12825
|
+
);
|
|
12092
12826
|
} catch (error) {
|
|
12093
12827
|
for (const id of ids) if (!attempted.has(id)) {
|
|
12094
12828
|
notifyFunding(options.onProgress, {
|
|
@@ -12178,7 +12912,7 @@ var OwneySDK = class {
|
|
|
12178
12912
|
progress(evidence);
|
|
12179
12913
|
adapterReported = false;
|
|
12180
12914
|
try {
|
|
12181
|
-
if (options.onBeforeSubmission)
|
|
12915
|
+
if (options.onBeforeSubmission || options.assertCurrent)
|
|
12182
12916
|
return await agent.withdraw(
|
|
12183
12917
|
state,
|
|
12184
12918
|
chainId,
|
|
@@ -12186,7 +12920,8 @@ var OwneySDK = class {
|
|
|
12186
12920
|
requested2,
|
|
12187
12921
|
progress,
|
|
12188
12922
|
options.gasMode ?? "auto",
|
|
12189
|
-
(next) => options.onBeforeSubmission({ ...evidence, ...next })
|
|
12923
|
+
options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
|
|
12924
|
+
options.assertCurrent
|
|
12190
12925
|
);
|
|
12191
12926
|
return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
12192
12927
|
} catch (error) {
|