@owney/sdk 0.7.27-beta.2 → 0.7.27-beta.3
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/chunk-LUQ4H72X.js +542 -0
- package/dist/index.cjs +1810 -715
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +1038 -529
- package/dist/token-batch-family-VW6ZJLK5.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// src/lib/token-batch-family.ts
|
|
2
|
+
import {
|
|
3
|
+
encodeAbiParameters,
|
|
4
|
+
encodeFunctionData,
|
|
5
|
+
hashTypedData,
|
|
6
|
+
isAddress,
|
|
7
|
+
isAddressEqual,
|
|
8
|
+
keccak256,
|
|
9
|
+
parseAbi as parseAbi2,
|
|
10
|
+
parseTransaction,
|
|
11
|
+
recoverTransactionAddress,
|
|
12
|
+
recoverTypedDataAddress,
|
|
13
|
+
sliceHex,
|
|
14
|
+
toBytes
|
|
15
|
+
} from "viem";
|
|
16
|
+
|
|
17
|
+
// src/errors.ts
|
|
18
|
+
var OwneyError = class extends Error {
|
|
19
|
+
code;
|
|
20
|
+
details;
|
|
21
|
+
agentId;
|
|
22
|
+
constructor(code, message, details, agentId) {
|
|
23
|
+
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
24
|
+
super(`${prefix} ${message}`);
|
|
25
|
+
this.name = "OwneyError";
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.details = details;
|
|
28
|
+
this.agentId = agentId;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var AgentNotFoundError = class extends OwneyError {
|
|
32
|
+
constructor(agentId, available) {
|
|
33
|
+
super(
|
|
34
|
+
"AGENT_NOT_FOUND",
|
|
35
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
36
|
+
{ agentId, available },
|
|
37
|
+
agentId
|
|
38
|
+
);
|
|
39
|
+
this.name = "AgentNotFoundError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var NotConnectedError = class extends OwneyError {
|
|
43
|
+
constructor() {
|
|
44
|
+
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
45
|
+
this.name = "NotConnectedError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var AgentChainIncompatibleError = class extends OwneyError {
|
|
49
|
+
incompatibleAgents;
|
|
50
|
+
connectedChainId;
|
|
51
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
52
|
+
const details = incompatibleAgents.map(
|
|
53
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
54
|
+
).join("; ");
|
|
55
|
+
super(
|
|
56
|
+
"AGENT_CHAIN_INCOMPATIBLE",
|
|
57
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
58
|
+
{ incompatibleAgents, connectedChainId }
|
|
59
|
+
);
|
|
60
|
+
this.name = "AgentChainIncompatibleError";
|
|
61
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
62
|
+
this.connectedChainId = connectedChainId;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/lib/permit2-batch.ts
|
|
67
|
+
import { parseAbi, hashStruct } from "viem";
|
|
68
|
+
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
69
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
70
|
+
var ARBITRUM_USDC_ADDRESS = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
|
|
71
|
+
function supportsSponsoredErc2612(chainId, token) {
|
|
72
|
+
const expected = chainId === 8453 ? BASE_USDC_ADDRESS : chainId === 42161 ? ARBITRUM_USDC_ADDRESS : void 0;
|
|
73
|
+
return expected !== void 0 && token.toLowerCase() === expected.toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
var ERC2612_PERMIT_TYPES = {
|
|
76
|
+
Permit: [
|
|
77
|
+
{ name: "owner", type: "address" },
|
|
78
|
+
{ name: "spender", type: "address" },
|
|
79
|
+
{ name: "value", type: "uint256" },
|
|
80
|
+
{ name: "nonce", type: "uint256" },
|
|
81
|
+
{ name: "deadline", type: "uint256" }
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
var ERC2612_READ_ABI = parseAbi([
|
|
85
|
+
"function name() view returns (string)",
|
|
86
|
+
"function version() view returns (string)",
|
|
87
|
+
"function nonces(address owner) view returns (uint256)"
|
|
88
|
+
]);
|
|
89
|
+
function erc2612TypedData(input) {
|
|
90
|
+
return {
|
|
91
|
+
domain: {
|
|
92
|
+
name: input.permit.tokenName,
|
|
93
|
+
version: input.permit.tokenVersion,
|
|
94
|
+
chainId: input.chainId,
|
|
95
|
+
verifyingContract: input.token
|
|
96
|
+
},
|
|
97
|
+
types: ERC2612_PERMIT_TYPES,
|
|
98
|
+
primaryType: "Permit",
|
|
99
|
+
message: {
|
|
100
|
+
owner: input.owner,
|
|
101
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
102
|
+
value: BigInt(input.permit.value),
|
|
103
|
+
nonce: BigInt(input.permit.nonce),
|
|
104
|
+
deadline: BigInt(input.permit.deadline)
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
var PERMIT_BATCH_TYPES = {
|
|
109
|
+
PermitBatchWitnessTransferFrom: [
|
|
110
|
+
{ name: "permitted", type: "TokenPermissions[]" },
|
|
111
|
+
{ name: "spender", type: "address" },
|
|
112
|
+
{ name: "nonce", type: "uint256" },
|
|
113
|
+
{ name: "deadline", type: "uint256" },
|
|
114
|
+
{ name: "witness", type: "Deposit" }
|
|
115
|
+
],
|
|
116
|
+
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
117
|
+
TokenPermissions: [
|
|
118
|
+
{ name: "token", type: "address" },
|
|
119
|
+
{ name: "amount", type: "uint256" }
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
var PERMIT2_BATCH_ABI = parseAbi([
|
|
123
|
+
"struct TokenPermissions { address token; uint256 amount; }",
|
|
124
|
+
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
125
|
+
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
126
|
+
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
127
|
+
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
128
|
+
]);
|
|
129
|
+
function batchPermit(b) {
|
|
130
|
+
return {
|
|
131
|
+
permitted: b.transfers.map((t) => ({
|
|
132
|
+
token: b.token,
|
|
133
|
+
amount: BigInt(t.amount)
|
|
134
|
+
})),
|
|
135
|
+
nonce: BigInt(b.nonce),
|
|
136
|
+
deadline: BigInt(b.deadline)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function batchTypedData(b, spender) {
|
|
140
|
+
return {
|
|
141
|
+
domain: {
|
|
142
|
+
name: "Permit2",
|
|
143
|
+
chainId: b.chainId,
|
|
144
|
+
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
145
|
+
},
|
|
146
|
+
types: PERMIT_BATCH_TYPES,
|
|
147
|
+
primaryType: "PermitBatchWitnessTransferFrom",
|
|
148
|
+
message: {
|
|
149
|
+
...batchPermit(b),
|
|
150
|
+
spender,
|
|
151
|
+
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
|
|
156
|
+
function batchWitness(b) {
|
|
157
|
+
return hashStruct({
|
|
158
|
+
types: PERMIT_BATCH_TYPES,
|
|
159
|
+
primaryType: "Deposit",
|
|
160
|
+
data: { recipients: b.transfers.map((t) => t.to) }
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function batchArgs(b) {
|
|
164
|
+
return [
|
|
165
|
+
batchPermit(b),
|
|
166
|
+
b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
|
|
167
|
+
b.from,
|
|
168
|
+
batchWitness(b),
|
|
169
|
+
BATCH_WITNESS_TYPE,
|
|
170
|
+
b.signature
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/lib/token-batch-family.ts
|
|
175
|
+
var memory = /* @__PURE__ */ new Map();
|
|
176
|
+
var settled = /* @__PURE__ */ new Map();
|
|
177
|
+
var hashPattern = /^0x[\da-f]{64}$/i;
|
|
178
|
+
var uint = (value) => typeof value === "string" && /^\d{1,78}$/.test(value) && BigInt(value) < 1n << 256n;
|
|
179
|
+
var same = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
180
|
+
var tokenBatchKey = (s) => `owney.token-batch.v1:${keccak256(toBytes(s.apiKey))}:${s.baseUrl ?? "default"}:${s.chainId}:${s.owner.toLowerCase()}:${s.token.toLowerCase()}`;
|
|
181
|
+
var tokenBatchAuthorizationId = (body, relayer) => keccak256(
|
|
182
|
+
encodeAbiParameters(
|
|
183
|
+
[{ type: "address" }, { type: "bytes32" }],
|
|
184
|
+
[body.from, hashTypedData(batchTypedData(body, relayer))]
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
var tokenBatchStoredBody = (body) => ({
|
|
188
|
+
...body,
|
|
189
|
+
transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
|
|
190
|
+
});
|
|
191
|
+
function tokenBatchRead(key) {
|
|
192
|
+
const raw = typeof window === "undefined" ? memory.get(key) : window.localStorage.getItem(key) ?? void 0;
|
|
193
|
+
return raw && settled.get(key) === raw ? void 0 : raw;
|
|
194
|
+
}
|
|
195
|
+
function tokenBatchWrite(key, value) {
|
|
196
|
+
if (typeof window === "undefined") memory.set(key, value);
|
|
197
|
+
else window.localStorage.setItem(key, value);
|
|
198
|
+
settled.delete(key);
|
|
199
|
+
if (tokenBatchRead(key) !== value)
|
|
200
|
+
throw pending("Deposit recovery data could not be saved.");
|
|
201
|
+
}
|
|
202
|
+
function tokenBatchClear(key, expected) {
|
|
203
|
+
if (tokenBatchRead(key) !== expected) return;
|
|
204
|
+
try {
|
|
205
|
+
if (typeof window === "undefined") memory.delete(key);
|
|
206
|
+
else window.localStorage.removeItem(key);
|
|
207
|
+
} catch {
|
|
208
|
+
settled.set(key, expected);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function keys() {
|
|
212
|
+
if (typeof window === "undefined") return [...memory.keys()];
|
|
213
|
+
const storage = window.localStorage;
|
|
214
|
+
return Array.from(
|
|
215
|
+
{ length: storage.length },
|
|
216
|
+
(_, i) => storage.key(i)
|
|
217
|
+
).filter((key) => key !== null);
|
|
218
|
+
}
|
|
219
|
+
function pending(message = "The saved deposit could not be verified. Its existing transactions remain pending.") {
|
|
220
|
+
return new OwneyError("DEPOSIT_IN_PROGRESS", message, {
|
|
221
|
+
sponsorPending: true,
|
|
222
|
+
notSubmitted: false,
|
|
223
|
+
safeToFallback: false
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
var approvalAbi = parseAbi2([
|
|
227
|
+
"function permit(address owner,address spender,uint256 value,uint256 deadline,uint8 v,bytes32 r,bytes32 s)"
|
|
228
|
+
]);
|
|
229
|
+
function tokenBatchCall(body, phase) {
|
|
230
|
+
if (phase === "deposit")
|
|
231
|
+
return {
|
|
232
|
+
to: BATCH_PERMIT2_ADDRESS,
|
|
233
|
+
data: encodeFunctionData({
|
|
234
|
+
abi: PERMIT2_BATCH_ABI,
|
|
235
|
+
functionName: "permitWitnessTransferFrom",
|
|
236
|
+
args: batchArgs(body)
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
const p = body.erc2612Permit;
|
|
240
|
+
let v = Number(sliceHex(p.signature, 64));
|
|
241
|
+
if (v < 27) v += 27;
|
|
242
|
+
return {
|
|
243
|
+
to: body.token,
|
|
244
|
+
data: encodeFunctionData({
|
|
245
|
+
abi: approvalAbi,
|
|
246
|
+
functionName: "permit",
|
|
247
|
+
args: [
|
|
248
|
+
body.from,
|
|
249
|
+
BATCH_PERMIT2_ADDRESS,
|
|
250
|
+
BigInt(p.value),
|
|
251
|
+
BigInt(p.deadline),
|
|
252
|
+
v,
|
|
253
|
+
sliceHex(p.signature, 0, 32),
|
|
254
|
+
sliceHex(p.signature, 32, 64)
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function validateBody(scope, body, relayer) {
|
|
260
|
+
if (!body || body.chainId !== scope.chainId || !isAddressEqual(body.from, scope.owner) || !isAddressEqual(body.token, scope.token) || !uint(body.nonce) || !uint(body.deadline) || BigInt(body.deadline) === 0n || !Array.isArray(body.transfers) || !body.transfers.length || body.transfers.length > 16 || body.transfers.some(
|
|
261
|
+
(t) => !isAddress(t.to) || !uint(t.amount) || BigInt(t.amount) === 0n
|
|
262
|
+
) || new Set(body.transfers.map((t) => t.to.toLowerCase())).size !== body.transfers.length)
|
|
263
|
+
throw pending();
|
|
264
|
+
if (!isAddressEqual(
|
|
265
|
+
await recoverTypedDataAddress({
|
|
266
|
+
...batchTypedData(body, relayer),
|
|
267
|
+
signature: body.signature
|
|
268
|
+
}),
|
|
269
|
+
body.from
|
|
270
|
+
))
|
|
271
|
+
throw pending();
|
|
272
|
+
const p = body.erc2612Permit;
|
|
273
|
+
if (p) {
|
|
274
|
+
if (!supportsSponsoredErc2612(body.chainId, body.token) || !uint(p.nonce) || !uint(p.deadline) || !uint(p.value) || BigInt(p.value) !== (1n << 256n) - 1n || p.deadline !== body.deadline || typeof p.tokenName !== "string" || typeof p.tokenVersion !== "string")
|
|
275
|
+
throw pending();
|
|
276
|
+
if (!isAddressEqual(
|
|
277
|
+
await recoverTypedDataAddress({
|
|
278
|
+
...erc2612TypedData({
|
|
279
|
+
chainId: body.chainId,
|
|
280
|
+
token: body.token,
|
|
281
|
+
owner: body.from,
|
|
282
|
+
permit: p
|
|
283
|
+
}),
|
|
284
|
+
signature: p.signature
|
|
285
|
+
}),
|
|
286
|
+
body.from
|
|
287
|
+
))
|
|
288
|
+
throw pending();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function validateTokenBatchAttempt(scope, body, attempt, expectedRelayer) {
|
|
292
|
+
if (!attempt || !["approval", "deposit"].includes(attempt.phase) || !hashPattern.test(attempt.hash) || !uint(attempt.nonce) || !Number.isFinite(attempt.preparedAt) || typeof attempt.submissionAttempted !== "boolean" || attempt.predecessorHash !== void 0 && !hashPattern.test(attempt.predecessorHash))
|
|
293
|
+
throw pending();
|
|
294
|
+
if (typeof attempt.serializedTransaction !== "string" || !/^0x(?:[\da-f]{2}){1,32768}$/i.test(attempt.serializedTransaction))
|
|
295
|
+
throw pending();
|
|
296
|
+
const serialized = attempt.serializedTransaction;
|
|
297
|
+
if (!same(keccak256(serialized), attempt.hash)) throw pending();
|
|
298
|
+
const tx = parseTransaction(serialized), relayer = await recoverTransactionAddress({
|
|
299
|
+
serializedTransaction: serialized
|
|
300
|
+
});
|
|
301
|
+
if (expectedRelayer && !isAddressEqual(relayer, expectedRelayer))
|
|
302
|
+
throw pending();
|
|
303
|
+
await validateBody(scope, body, relayer);
|
|
304
|
+
const call = tokenBatchCall(body, attempt.phase);
|
|
305
|
+
if (tx.chainId !== body.chainId || !tx.to || !isAddressEqual(tx.to, call.to) || (tx.value ?? 0n) !== 0n || tx.data !== call.data || !Number.isSafeInteger(tx.nonce) || String(tx.nonce) !== attempt.nonce)
|
|
306
|
+
throw pending();
|
|
307
|
+
return relayer;
|
|
308
|
+
}
|
|
309
|
+
function terminalOf(value, attempts) {
|
|
310
|
+
if (!value || !["confirmed", "failed", "expired"].includes(value.kind) || !uint(value.blockNumber))
|
|
311
|
+
return;
|
|
312
|
+
if (value.kind === "confirmed" && (!value.txHash || !attempts.some(
|
|
313
|
+
(a) => a.phase === "deposit" && same(a.hash, value.txHash)
|
|
314
|
+
)))
|
|
315
|
+
return;
|
|
316
|
+
if (value.txHash !== void 0 && !hashPattern.test(value.txHash)) return;
|
|
317
|
+
return {
|
|
318
|
+
kind: value.kind,
|
|
319
|
+
blockNumber: value.blockNumber,
|
|
320
|
+
...value.txHash ? { txHash: value.txHash } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
async function validateFamily(scope, raw) {
|
|
324
|
+
if (!raw || raw.version !== 1 || !Array.isArray(raw.attempts) || !raw.attempts.length || !raw.current || !isAddress(raw.relayer) || !hashPattern.test(raw.authorizationId))
|
|
325
|
+
throw pending();
|
|
326
|
+
if (!same(tokenBatchAuthorizationId(raw.body, raw.relayer), raw.authorizationId))
|
|
327
|
+
throw pending();
|
|
328
|
+
const seen = /* @__PURE__ */ new Map();
|
|
329
|
+
const roots = /* @__PURE__ */ new Set();
|
|
330
|
+
for (const a of raw.attempts) {
|
|
331
|
+
await validateTokenBatchAttempt(scope, raw.body, a, raw.relayer);
|
|
332
|
+
if (seen.has(a.hash.toLowerCase())) throw pending();
|
|
333
|
+
if (a.predecessorHash) {
|
|
334
|
+
const predecessor = seen.get(a.predecessorHash.toLowerCase());
|
|
335
|
+
if (!predecessor || predecessor.phase !== a.phase || BigInt(a.nonce) <= BigInt(predecessor.nonce))
|
|
336
|
+
throw pending();
|
|
337
|
+
} else {
|
|
338
|
+
if (roots.has(a.phase)) throw pending();
|
|
339
|
+
roots.add(a.phase);
|
|
340
|
+
}
|
|
341
|
+
seen.set(a.hash.toLowerCase(), a);
|
|
342
|
+
}
|
|
343
|
+
for (const phase of ["approval", "deposit"]) {
|
|
344
|
+
const current = raw.current[phase];
|
|
345
|
+
if (current && !raw.attempts.some((a) => a.phase === phase && same(a.hash, current)))
|
|
346
|
+
throw pending();
|
|
347
|
+
if (raw.attempts.some((a) => a.phase === phase) && !current)
|
|
348
|
+
throw pending();
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
...raw,
|
|
352
|
+
body: tokenBatchStoredBody(raw.body),
|
|
353
|
+
terminal: terminalOf(raw.terminal, raw.attempts)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function importBody(scope, body) {
|
|
357
|
+
const attempts = [];
|
|
358
|
+
let relayer;
|
|
359
|
+
for (const phase of ["approval", "deposit"]) {
|
|
360
|
+
const bytes = phase === "approval" ? body.serializedPermitTransaction : body.serializedTransaction;
|
|
361
|
+
if (!bytes) continue;
|
|
362
|
+
const a = {
|
|
363
|
+
phase,
|
|
364
|
+
hash: keccak256(bytes),
|
|
365
|
+
serializedTransaction: bytes,
|
|
366
|
+
nonce: String(parseTransaction(bytes).nonce),
|
|
367
|
+
preparedAt: 0,
|
|
368
|
+
submissionAttempted: true
|
|
369
|
+
};
|
|
370
|
+
relayer = await validateTokenBatchAttempt(scope, body, a, relayer);
|
|
371
|
+
attempts.push(a);
|
|
372
|
+
}
|
|
373
|
+
if (!relayer) return;
|
|
374
|
+
return {
|
|
375
|
+
version: 1,
|
|
376
|
+
authorizationId: tokenBatchAuthorizationId(body, relayer),
|
|
377
|
+
relayer,
|
|
378
|
+
body: tokenBatchStoredBody(body),
|
|
379
|
+
attempts,
|
|
380
|
+
current: Object.fromEntries(attempts.map((a) => [a.phase, a.hash]))
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function merge(base, incoming) {
|
|
384
|
+
if (!same(base.authorizationId, incoming.authorizationId)) throw pending();
|
|
385
|
+
const attempts = base.attempts.map((a) => ({ ...a }));
|
|
386
|
+
for (const a of incoming.attempts) {
|
|
387
|
+
const existing = attempts.find((x) => same(x.hash, a.hash));
|
|
388
|
+
if (existing) existing.submissionAttempted ||= a.submissionAttempted;
|
|
389
|
+
else attempts.push(a);
|
|
390
|
+
}
|
|
391
|
+
return { ...base, attempts };
|
|
392
|
+
}
|
|
393
|
+
async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
|
|
394
|
+
try {
|
|
395
|
+
const key = tokenBatchKey(scope), records = /* @__PURE__ */ new Map();
|
|
396
|
+
for (const k of keys().filter((k2) => k2.startsWith(`${key}:family:`))) {
|
|
397
|
+
const raw = tokenBatchRead(k);
|
|
398
|
+
if (!raw) continue;
|
|
399
|
+
const family = await validateFamily(scope, JSON.parse(raw));
|
|
400
|
+
if (k !== `${key}:family:${family.authorizationId}`) throw pending();
|
|
401
|
+
records.set(family.authorizationId, family);
|
|
402
|
+
}
|
|
403
|
+
let projectedId;
|
|
404
|
+
const projection = tokenBatchRead(key);
|
|
405
|
+
if (projection) {
|
|
406
|
+
const imported = await importBody(scope, JSON.parse(projection));
|
|
407
|
+
if (imported) {
|
|
408
|
+
projectedId = imported.authorizationId;
|
|
409
|
+
const stored = records.get(projectedId);
|
|
410
|
+
if (stored)
|
|
411
|
+
for (const attempt of imported.attempts) {
|
|
412
|
+
const known = stored.attempts.find(
|
|
413
|
+
(a) => same(a.hash, attempt.hash)
|
|
414
|
+
);
|
|
415
|
+
if (known) attempt.submissionAttempted = known.submissionAttempted;
|
|
416
|
+
}
|
|
417
|
+
records.set(
|
|
418
|
+
projectedId,
|
|
419
|
+
stored ? await validateFamily(scope, merge(stored, imported)) : imported
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (expectedHash) {
|
|
424
|
+
let found = [...records.values()].filter(
|
|
425
|
+
(f) => f.attempts.some((a) => same(a.hash, expectedHash))
|
|
426
|
+
);
|
|
427
|
+
if (!found.length) {
|
|
428
|
+
const archive = tokenBatchRead(
|
|
429
|
+
`${key}:expired:${expectedHash.toLowerCase()}`
|
|
430
|
+
);
|
|
431
|
+
if (archive) {
|
|
432
|
+
const f = await importBody(scope, JSON.parse(archive));
|
|
433
|
+
if (f?.attempts.some((a) => same(a.hash, expectedHash))) found = [f];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (!found.length && allowMissing) return;
|
|
437
|
+
if (found.length !== 1) throw pending();
|
|
438
|
+
return found[0];
|
|
439
|
+
}
|
|
440
|
+
const active = [...records.values()].filter(
|
|
441
|
+
(f) => !f.terminal || f.authorizationId === projectedId
|
|
442
|
+
);
|
|
443
|
+
if (active.length > 1) throw pending();
|
|
444
|
+
return active[0];
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof OwneyError) throw error;
|
|
447
|
+
throw pending();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function saveTokenBatchAttempt(scope, body, attempt, assertCurrent = () => {
|
|
451
|
+
}) {
|
|
452
|
+
try {
|
|
453
|
+
const relayer = await validateTokenBatchAttempt(scope, body, attempt), id = tokenBatchAuthorizationId(body, relayer), key = tokenBatchKey(scope), sidecar = `${key}:family:${id}`;
|
|
454
|
+
const prior = await loadTokenBatchFamily(scope);
|
|
455
|
+
if (prior && prior.authorizationId !== id) throw pending();
|
|
456
|
+
let family = prior ?? {
|
|
457
|
+
version: 1,
|
|
458
|
+
authorizationId: id,
|
|
459
|
+
relayer,
|
|
460
|
+
body: tokenBatchStoredBody(body),
|
|
461
|
+
attempts: [],
|
|
462
|
+
current: {}
|
|
463
|
+
};
|
|
464
|
+
const saved = tokenBatchRead(sidecar);
|
|
465
|
+
if (saved)
|
|
466
|
+
family = merge(await validateFamily(scope, JSON.parse(saved)), family);
|
|
467
|
+
const sameAttempt = family.attempts.find((a) => same(a.hash, attempt.hash));
|
|
468
|
+
if (sameAttempt)
|
|
469
|
+
sameAttempt.submissionAttempted ||= attempt.submissionAttempted;
|
|
470
|
+
else family.attempts.push({ ...attempt });
|
|
471
|
+
family.current[attempt.phase] = attempt.hash;
|
|
472
|
+
family.body = { ...tokenBatchStoredBody(body) };
|
|
473
|
+
for (const phase of ["approval", "deposit"]) {
|
|
474
|
+
const current = family.attempts.find(
|
|
475
|
+
(a) => a.hash === family.current[phase]
|
|
476
|
+
);
|
|
477
|
+
if (current)
|
|
478
|
+
family.body[phase === "deposit" ? "serializedTransaction" : "serializedPermitTransaction"] = current.serializedTransaction;
|
|
479
|
+
}
|
|
480
|
+
family = await validateFamily(scope, family);
|
|
481
|
+
if (tokenBatchRead(sidecar) !== saved)
|
|
482
|
+
throw pending(
|
|
483
|
+
"Deposit recovery data changed. Retry the existing deposit."
|
|
484
|
+
);
|
|
485
|
+
assertCurrent();
|
|
486
|
+
tokenBatchWrite(sidecar, JSON.stringify(family));
|
|
487
|
+
assertCurrent();
|
|
488
|
+
tokenBatchWrite(key, JSON.stringify(family.body));
|
|
489
|
+
return family;
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof OwneyError) throw error;
|
|
492
|
+
throw pending(
|
|
493
|
+
"Deposit recovery data could not be saved. The earlier transaction remains tracked."
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function finishTokenBatchFamily(scope, family, terminal) {
|
|
498
|
+
const key = tokenBatchKey(scope), sidecar = `${key}:family:${family.authorizationId}`;
|
|
499
|
+
try {
|
|
500
|
+
const saved = tokenBatchRead(sidecar), current = saved ? JSON.parse(saved) : family;
|
|
501
|
+
if (current.authorizationId !== family.authorizationId) return;
|
|
502
|
+
if (terminal.kind !== "confirmed" && current.attempts.some(
|
|
503
|
+
(a) => !family.attempts.some((known) => same(known.hash, a.hash))
|
|
504
|
+
))
|
|
505
|
+
return;
|
|
506
|
+
const evidence = terminalOf(terminal, current.attempts);
|
|
507
|
+
if (!evidence) return;
|
|
508
|
+
tokenBatchWrite(
|
|
509
|
+
sidecar,
|
|
510
|
+
JSON.stringify({ ...current, terminal: evidence })
|
|
511
|
+
);
|
|
512
|
+
const projected = tokenBatchRead(key);
|
|
513
|
+
if (projected && tokenBatchAuthorizationId(JSON.parse(projected), family.relayer) === family.authorizationId)
|
|
514
|
+
tokenBatchClear(key, projected);
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export {
|
|
520
|
+
OwneyError,
|
|
521
|
+
AgentNotFoundError,
|
|
522
|
+
NotConnectedError,
|
|
523
|
+
AgentChainIncompatibleError,
|
|
524
|
+
BATCH_PERMIT2_ADDRESS,
|
|
525
|
+
supportsSponsoredErc2612,
|
|
526
|
+
ERC2612_READ_ABI,
|
|
527
|
+
erc2612TypedData,
|
|
528
|
+
PERMIT2_BATCH_ABI,
|
|
529
|
+
batchTypedData,
|
|
530
|
+
batchArgs,
|
|
531
|
+
tokenBatchKey,
|
|
532
|
+
tokenBatchAuthorizationId,
|
|
533
|
+
tokenBatchStoredBody,
|
|
534
|
+
tokenBatchRead,
|
|
535
|
+
tokenBatchWrite,
|
|
536
|
+
tokenBatchClear,
|
|
537
|
+
tokenBatchCall,
|
|
538
|
+
validateTokenBatchAttempt,
|
|
539
|
+
loadTokenBatchFamily,
|
|
540
|
+
saveTokenBatchAttempt,
|
|
541
|
+
finishTokenBatchFamily
|
|
542
|
+
};
|