@veil-cash/sdk 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -37
- package/SDK.md +55 -1
- package/dist/cli/index.cjs +806 -22
- package/dist/index.cjs +554 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +486 -1
- package/dist/index.d.ts +486 -1
- package/dist/index.js +537 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/veil/SKILL.md +74 -2
- package/skills/veil/reference.md +71 -2
- package/src/abi.ts +172 -0
- package/src/addresses.ts +14 -0
- package/src/cli/commands/subaccount.ts +355 -0
- package/src/cli/errors.ts +4 -0
- package/src/cli/index.ts +5 -1
- package/src/cli/wallet.ts +2 -2
- package/src/index.ts +35 -0
- package/src/relay.ts +45 -24
- package/src/subaccount.ts +481 -0
- package/src/types.ts +134 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { isAddress } from 'viem';
|
|
3
|
+
import {
|
|
4
|
+
buildSubaccountRecoveryTx,
|
|
5
|
+
deploySubaccountForwarder,
|
|
6
|
+
deriveSubaccountSlot,
|
|
7
|
+
getSubaccountStatus,
|
|
8
|
+
isSubaccountForwarderDeployed,
|
|
9
|
+
MAX_SUBACCOUNT_SLOTS,
|
|
10
|
+
sweepSubaccountForwarder,
|
|
11
|
+
} from '../../subaccount.js';
|
|
12
|
+
import { getConfig } from '../config.js';
|
|
13
|
+
import { CLIError, ErrorCode, handleCLIError } from '../errors.js';
|
|
14
|
+
import { printFields, printHeader, printJson, printLine, printList, printSection, txUrl } from '../output.js';
|
|
15
|
+
import { sendTransaction } from '../wallet.js';
|
|
16
|
+
import type { SubaccountAsset } from '../../types.js';
|
|
17
|
+
|
|
18
|
+
function parseSlotValue(raw: string): number {
|
|
19
|
+
const normalized = raw.trim();
|
|
20
|
+
if (!/^\d+$/.test(normalized)) {
|
|
21
|
+
throw new CLIError(ErrorCode.INVALID_SLOT, '--slot must be a non-negative integer');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const slot = Number(normalized);
|
|
25
|
+
if (slot >= MAX_SUBACCOUNT_SLOTS) {
|
|
26
|
+
throw new CLIError(
|
|
27
|
+
ErrorCode.INVALID_SLOT,
|
|
28
|
+
`--slot must be 0-${MAX_SUBACCOUNT_SLOTS - 1} (max ${MAX_SUBACCOUNT_SLOTS} subaccounts supported)`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return slot;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getRequiredVeilKey(): `0x${string}` {
|
|
36
|
+
const veilKey = process.env.VEIL_KEY;
|
|
37
|
+
if (!veilKey) {
|
|
38
|
+
throw new CLIError(ErrorCode.VEIL_KEY_MISSING, 'VEIL_KEY required. Set VEIL_KEY env');
|
|
39
|
+
}
|
|
40
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(veilKey)) {
|
|
41
|
+
throw new CLIError(ErrorCode.VEIL_KEY_MISSING, 'VEIL_KEY must be a 0x-prefixed 32-byte hex string');
|
|
42
|
+
}
|
|
43
|
+
return veilKey as `0x${string}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseAsset(raw: string): SubaccountAsset {
|
|
47
|
+
const asset = raw.toLowerCase();
|
|
48
|
+
if (asset !== 'eth' && asset !== 'usdc') {
|
|
49
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${raw}. Supported: eth, usdc`);
|
|
50
|
+
}
|
|
51
|
+
return asset;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printQueueHuman(
|
|
55
|
+
title: string,
|
|
56
|
+
queue: {
|
|
57
|
+
queueBalance: string;
|
|
58
|
+
pendingCount: number;
|
|
59
|
+
pendingDeposits: Array<{ nonce: string; amount: string; status: string }>;
|
|
60
|
+
},
|
|
61
|
+
): void {
|
|
62
|
+
printSection(title);
|
|
63
|
+
printFields([
|
|
64
|
+
{ label: 'Queue balance', value: queue.queueBalance },
|
|
65
|
+
{ label: 'Pending', value: queue.pendingCount },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
if (queue.pendingDeposits.length > 0) {
|
|
69
|
+
printList(
|
|
70
|
+
queue.pendingDeposits.map((deposit) => `nonce ${deposit.nonce}: ${deposit.amount} (${deposit.status})`),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createSubaccountCommand(): Command {
|
|
76
|
+
const subaccount = new Command('subaccount')
|
|
77
|
+
.description('Manage Veil subaccounts')
|
|
78
|
+
.addHelpText('after', `
|
|
79
|
+
Examples:
|
|
80
|
+
veil subaccount derive --slot 0
|
|
81
|
+
veil subaccount status --slot 0
|
|
82
|
+
veil subaccount deploy --slot 0
|
|
83
|
+
veil subaccount sweep --slot 0 --asset eth
|
|
84
|
+
veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
|
|
85
|
+
veil subaccount address --slot 0
|
|
86
|
+
`);
|
|
87
|
+
|
|
88
|
+
subaccount
|
|
89
|
+
.command('derive')
|
|
90
|
+
.description('Derive subaccount metadata for a slot')
|
|
91
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
92
|
+
.option('--json', 'Output as JSON')
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
try {
|
|
95
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
96
|
+
const rpcUrl = process.env.RPC_URL;
|
|
97
|
+
const slot = await deriveSubaccountSlot({
|
|
98
|
+
rootPrivateKey,
|
|
99
|
+
slot: options.slot,
|
|
100
|
+
rpcUrl,
|
|
101
|
+
});
|
|
102
|
+
const deployed = await isSubaccountForwarderDeployed({
|
|
103
|
+
forwarderAddress: slot.forwarderAddress,
|
|
104
|
+
rpcUrl,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const output = {
|
|
108
|
+
...slot,
|
|
109
|
+
deployed,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (options.json) {
|
|
113
|
+
printJson(output);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
printHeader(`Subaccount Slot ${slot.slot}`);
|
|
118
|
+
printFields([
|
|
119
|
+
{ label: 'Child owner', value: slot.childOwner },
|
|
120
|
+
{ label: 'Deposit key', value: slot.childDepositKey },
|
|
121
|
+
{ label: 'Salt', value: slot.salt },
|
|
122
|
+
{ label: 'Forwarder', value: slot.forwarderAddress },
|
|
123
|
+
{ label: 'Deployed', value: deployed },
|
|
124
|
+
]);
|
|
125
|
+
printLine();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
handleCLIError(error);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
subaccount
|
|
132
|
+
.command('status')
|
|
133
|
+
.description('Show subaccount deployment, balances, and queue state')
|
|
134
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
135
|
+
.option('--json', 'Output as JSON')
|
|
136
|
+
.action(async (options) => {
|
|
137
|
+
try {
|
|
138
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
139
|
+
const status = await getSubaccountStatus({
|
|
140
|
+
rootPrivateKey,
|
|
141
|
+
slot: options.slot,
|
|
142
|
+
rpcUrl: process.env.RPC_URL,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (options.json) {
|
|
146
|
+
printJson(status);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
printHeader(`Subaccount Slot ${status.slot.slot}`);
|
|
151
|
+
printFields([
|
|
152
|
+
{ label: 'Forwarder', value: status.slot.forwarderAddress },
|
|
153
|
+
{ label: 'Child owner', value: status.slot.childOwner },
|
|
154
|
+
{ label: 'Deposit key', value: status.slot.childDepositKey },
|
|
155
|
+
{ label: 'Salt', value: status.slot.salt },
|
|
156
|
+
{ label: 'Deployed', value: status.deployed },
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
printSection('Forwarder Balances');
|
|
160
|
+
printFields([
|
|
161
|
+
{ label: 'ETH', value: `${status.balances.eth.balance} ETH` },
|
|
162
|
+
{ label: 'USDC', value: `${status.balances.usdc.balance} USDC` },
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
printQueueHuman('ETH Queue', status.queues.eth);
|
|
166
|
+
printQueueHuman('USDC Queue', status.queues.usdc);
|
|
167
|
+
printLine();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
handleCLIError(error);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
subaccount
|
|
174
|
+
.command('deploy')
|
|
175
|
+
.description('Deploy a subaccount forwarder through the relay')
|
|
176
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
177
|
+
.option('--json', 'Output as JSON')
|
|
178
|
+
.action(async (options) => {
|
|
179
|
+
try {
|
|
180
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
181
|
+
const result = await deploySubaccountForwarder({
|
|
182
|
+
rootPrivateKey,
|
|
183
|
+
slot: options.slot,
|
|
184
|
+
rpcUrl: process.env.RPC_URL,
|
|
185
|
+
relayUrl: process.env.RELAY_URL,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const output = {
|
|
189
|
+
...result,
|
|
190
|
+
slot: options.slot,
|
|
191
|
+
forwarderAddress: result.slot.forwarderAddress,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (options.json) {
|
|
195
|
+
printJson(output);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
printHeader('Subaccount Deploy Submitted');
|
|
200
|
+
printFields([
|
|
201
|
+
{ label: 'Slot', value: options.slot },
|
|
202
|
+
{ label: 'Forwarder', value: result.slot.forwarderAddress },
|
|
203
|
+
{ label: 'Transaction', value: txUrl(result.transactionHash) },
|
|
204
|
+
{ label: 'Block', value: result.blockNumber },
|
|
205
|
+
]);
|
|
206
|
+
printLine();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
handleCLIError(error);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
subaccount
|
|
213
|
+
.command('sweep')
|
|
214
|
+
.description('Sweep ETH or USDC from a subaccount forwarder through the relay')
|
|
215
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
216
|
+
.requiredOption('--asset <asset>', 'Asset to sweep (eth or usdc)', parseAsset)
|
|
217
|
+
.option('--json', 'Output as JSON')
|
|
218
|
+
.action(async (options) => {
|
|
219
|
+
try {
|
|
220
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
221
|
+
const slot = await deriveSubaccountSlot({
|
|
222
|
+
rootPrivateKey,
|
|
223
|
+
slot: options.slot,
|
|
224
|
+
rpcUrl: process.env.RPC_URL,
|
|
225
|
+
});
|
|
226
|
+
const result = await sweepSubaccountForwarder({
|
|
227
|
+
forwarderAddress: slot.forwarderAddress,
|
|
228
|
+
asset: options.asset,
|
|
229
|
+
relayUrl: process.env.RELAY_URL,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const output = {
|
|
233
|
+
...result,
|
|
234
|
+
slot: options.slot,
|
|
235
|
+
asset: options.asset,
|
|
236
|
+
forwarderAddress: slot.forwarderAddress,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (options.json) {
|
|
240
|
+
printJson(output);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
printHeader('Subaccount Sweep Submitted');
|
|
245
|
+
printFields([
|
|
246
|
+
{ label: 'Slot', value: options.slot },
|
|
247
|
+
{ label: 'Asset', value: options.asset.toUpperCase() },
|
|
248
|
+
{ label: 'Forwarder', value: slot.forwarderAddress },
|
|
249
|
+
{ label: 'Transaction', value: txUrl(result.transactionHash) },
|
|
250
|
+
{ label: 'Block', value: result.blockNumber },
|
|
251
|
+
]);
|
|
252
|
+
printLine();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
handleCLIError(error);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
subaccount
|
|
259
|
+
.command('recover')
|
|
260
|
+
.description('Recover assets sitting on the subaccount forwarder with a direct withdraw transaction')
|
|
261
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
262
|
+
.requiredOption('--asset <asset>', 'Asset to recover (eth or usdc)', parseAsset)
|
|
263
|
+
.requiredOption('--to <address>', 'Recipient address')
|
|
264
|
+
.requiredOption('--amount <value>', 'Amount to recover')
|
|
265
|
+
.option('--json', 'Output as JSON')
|
|
266
|
+
.action(async (options) => {
|
|
267
|
+
try {
|
|
268
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
269
|
+
if (!isAddress(options.to)) {
|
|
270
|
+
throw new CLIError(ErrorCode.INVALID_ADDRESS, `Invalid recipient address: ${options.to}`);
|
|
271
|
+
}
|
|
272
|
+
if (!process.env.WALLET_KEY) {
|
|
273
|
+
throw new CLIError(
|
|
274
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
275
|
+
'WALLET_KEY required for recovery. Recovery submits a transaction on-chain and needs a gas payer.',
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const config = getConfig({});
|
|
279
|
+
const recovery = await buildSubaccountRecoveryTx({
|
|
280
|
+
rootPrivateKey,
|
|
281
|
+
slot: options.slot,
|
|
282
|
+
asset: options.asset,
|
|
283
|
+
to: options.to as `0x${string}`,
|
|
284
|
+
amount: options.amount,
|
|
285
|
+
rpcUrl: process.env.RPC_URL,
|
|
286
|
+
});
|
|
287
|
+
const result = await sendTransaction(config, recovery.transaction);
|
|
288
|
+
|
|
289
|
+
const output = {
|
|
290
|
+
success: result.receipt.status === 'success',
|
|
291
|
+
slot: options.slot,
|
|
292
|
+
asset: recovery.asset,
|
|
293
|
+
amount: recovery.amount,
|
|
294
|
+
amountWei: recovery.amountWei,
|
|
295
|
+
forwarderAddress: recovery.forwarderAddress,
|
|
296
|
+
recipient: recovery.recipient,
|
|
297
|
+
nonce: recovery.nonce,
|
|
298
|
+
deadline: recovery.deadline,
|
|
299
|
+
signature: recovery.signature,
|
|
300
|
+
transactionHash: result.hash,
|
|
301
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if (options.json) {
|
|
305
|
+
printJson(output);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
printHeader('Subaccount Recovery Submitted');
|
|
310
|
+
printFields([
|
|
311
|
+
{ label: 'Slot', value: options.slot },
|
|
312
|
+
{ label: 'Asset', value: recovery.asset.toUpperCase() },
|
|
313
|
+
{ label: 'Amount', value: recovery.amount },
|
|
314
|
+
{ label: 'Recipient', value: recovery.recipient },
|
|
315
|
+
{ label: 'Forwarder', value: recovery.forwarderAddress },
|
|
316
|
+
{ label: 'Nonce', value: recovery.nonce },
|
|
317
|
+
{ label: 'Transaction', value: txUrl(result.hash) },
|
|
318
|
+
{ label: 'Block', value: result.receipt.blockNumber },
|
|
319
|
+
]);
|
|
320
|
+
printLine();
|
|
321
|
+
} catch (error) {
|
|
322
|
+
handleCLIError(error);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
subaccount
|
|
327
|
+
.command('address')
|
|
328
|
+
.description('Print the predicted forwarder address for a subaccount slot')
|
|
329
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
330
|
+
.option('--json', 'Output as JSON')
|
|
331
|
+
.action(async (options) => {
|
|
332
|
+
try {
|
|
333
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
334
|
+
const slot = await deriveSubaccountSlot({
|
|
335
|
+
rootPrivateKey,
|
|
336
|
+
slot: options.slot,
|
|
337
|
+
rpcUrl: process.env.RPC_URL,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (options.json) {
|
|
341
|
+
printJson({
|
|
342
|
+
slot: options.slot,
|
|
343
|
+
forwarderAddress: slot.forwarderAddress,
|
|
344
|
+
});
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
printLine(slot.forwarderAddress);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
handleCLIError(error);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return subaccount;
|
|
355
|
+
}
|
package/src/cli/errors.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const ErrorCode = {
|
|
|
11
11
|
DEPOSIT_KEY_MISSING: 'DEPOSIT_KEY_MISSING',
|
|
12
12
|
CONFIG_CONFLICT: 'CONFIG_CONFLICT',
|
|
13
13
|
INVALID_ADDRESS: 'INVALID_ADDRESS',
|
|
14
|
+
INVALID_SLOT: 'INVALID_SLOT',
|
|
14
15
|
INVALID_AMOUNT: 'INVALID_AMOUNT',
|
|
15
16
|
INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
|
|
16
17
|
USER_NOT_REGISTERED: 'USER_NOT_REGISTERED',
|
|
@@ -58,6 +59,9 @@ function inferErrorCode(message: string): ErrorCodeType {
|
|
|
58
59
|
if (msg.includes('invalid') && msg.includes('address')) {
|
|
59
60
|
return ErrorCode.INVALID_ADDRESS;
|
|
60
61
|
}
|
|
62
|
+
if (msg.includes('invalid') && msg.includes('slot')) {
|
|
63
|
+
return ErrorCode.INVALID_SLOT;
|
|
64
|
+
}
|
|
61
65
|
if (msg.includes('insufficient balance') || msg.includes('not enough')) {
|
|
62
66
|
return ErrorCode.INSUFFICIENT_BALANCE;
|
|
63
67
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* veil withdraw ETH 0.1 0x... # Withdraw to public address
|
|
14
14
|
* veil transfer ETH 0.1 0x... # Transfer privately
|
|
15
15
|
* veil merge ETH 0.5 # Merge UTXOs (self-transfer)
|
|
16
|
+
* veil subaccount status --slot 0 # Check subaccount status
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { Command } from 'commander';
|
|
@@ -27,6 +28,7 @@ import { createPrivateBalanceCommand } from './commands/private-balance.js';
|
|
|
27
28
|
import { createWithdrawCommand } from './commands/withdraw.js';
|
|
28
29
|
import { createTransferCommand, createMergeCommand } from './commands/transfer.js';
|
|
29
30
|
import { createStatusCommand } from './commands/status.js';
|
|
31
|
+
import { createSubaccountCommand } from './commands/subaccount.js';
|
|
30
32
|
|
|
31
33
|
// Load environment variables
|
|
32
34
|
loadEnv();
|
|
@@ -36,13 +38,14 @@ const program = new Command();
|
|
|
36
38
|
program
|
|
37
39
|
.name('veil')
|
|
38
40
|
.description('CLI for Veil Cash privacy pools on Base')
|
|
39
|
-
.version('0.
|
|
41
|
+
.version('0.6.1')
|
|
40
42
|
.addHelpText('after', `
|
|
41
43
|
Getting started:
|
|
42
44
|
veil init
|
|
43
45
|
veil register
|
|
44
46
|
veil deposit ETH 0.1
|
|
45
47
|
veil balance
|
|
48
|
+
veil subaccount status --slot 0
|
|
46
49
|
`);
|
|
47
50
|
|
|
48
51
|
// Add commands
|
|
@@ -57,6 +60,7 @@ program.addCommand(createWithdrawCommand());
|
|
|
57
60
|
program.addCommand(createTransferCommand());
|
|
58
61
|
program.addCommand(createMergeCommand());
|
|
59
62
|
program.addCommand(createStatusCommand());
|
|
63
|
+
program.addCommand(createSubaccountCommand());
|
|
60
64
|
|
|
61
65
|
const knownTopLevelCommands = new Set([
|
|
62
66
|
...program.commands.map((command) => command.name()),
|
package/src/cli/wallet.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
15
15
|
import { base } from 'viem/chains';
|
|
16
16
|
import type { TransactionData } from '../types.js';
|
|
17
|
-
import { ENTRY_ABI, ERC20_ABI } from '../abi.js';
|
|
17
|
+
import { ENTRY_ABI, ERC20_ABI, FORWARDER_ABI } from '../abi.js';
|
|
18
18
|
import { getAddresses, POOL_CONFIG, ADDRESSES } from '../addresses.js';
|
|
19
19
|
|
|
20
20
|
export interface WalletConfig {
|
|
@@ -118,7 +118,7 @@ function decodeCustomError(error: unknown): string | null {
|
|
|
118
118
|
|
|
119
119
|
if (possibleData && typeof possibleData === 'string' && possibleData.startsWith('0x')) {
|
|
120
120
|
try {
|
|
121
|
-
for (const abi of [ENTRY_ABI] as const) {
|
|
121
|
+
for (const abi of [ENTRY_ABI, FORWARDER_ABI] as const) {
|
|
122
122
|
try {
|
|
123
123
|
const decoded = decodeErrorResult({
|
|
124
124
|
abi,
|
package/src/index.ts
CHANGED
|
@@ -100,8 +100,10 @@ export type { ProofInput } from './prover.js';
|
|
|
100
100
|
// Addresses and config
|
|
101
101
|
export {
|
|
102
102
|
ADDRESSES,
|
|
103
|
+
FORWARDER_CONTRACT_VERSION,
|
|
103
104
|
POOL_CONFIG,
|
|
104
105
|
getAddresses,
|
|
106
|
+
getForwarderFactoryAddress,
|
|
105
107
|
getPoolAddress,
|
|
106
108
|
getQueueAddress,
|
|
107
109
|
getRelayUrl,
|
|
@@ -119,10 +121,32 @@ export {
|
|
|
119
121
|
export {
|
|
120
122
|
ENTRY_ABI,
|
|
121
123
|
ERC20_ABI,
|
|
124
|
+
FORWARDER_ABI,
|
|
125
|
+
FORWARDER_FACTORY_ABI,
|
|
122
126
|
QUEUE_ABI,
|
|
123
127
|
POOL_ABI,
|
|
124
128
|
} from './abi.js';
|
|
125
129
|
|
|
130
|
+
// Subaccount functions
|
|
131
|
+
export {
|
|
132
|
+
MAX_SUBACCOUNT_SLOTS,
|
|
133
|
+
deriveSubaccountChildPrivateKey,
|
|
134
|
+
deriveSubaccountSalt,
|
|
135
|
+
deriveSubaccountChildOwner,
|
|
136
|
+
deriveSubaccountChildDepositKey,
|
|
137
|
+
deriveSubaccountSlot,
|
|
138
|
+
predictSubaccountForwarder,
|
|
139
|
+
isSubaccountForwarderDeployed,
|
|
140
|
+
deploySubaccountForwarder,
|
|
141
|
+
sweepSubaccountForwarder,
|
|
142
|
+
getSubaccountStatus,
|
|
143
|
+
buildSubaccountWithdrawTypedData,
|
|
144
|
+
signSubaccountWithdraw,
|
|
145
|
+
isSubaccountWithdrawNonceUsed,
|
|
146
|
+
findNextSubaccountWithdrawNonce,
|
|
147
|
+
buildSubaccountRecoveryTx,
|
|
148
|
+
} from './subaccount.js';
|
|
149
|
+
|
|
126
150
|
// Utilities
|
|
127
151
|
export {
|
|
128
152
|
poseidonHash,
|
|
@@ -166,4 +190,15 @@ export type {
|
|
|
166
190
|
WithdrawResult,
|
|
167
191
|
TransferResult,
|
|
168
192
|
UtxoSelectionResult,
|
|
193
|
+
SubaccountAsset,
|
|
194
|
+
SubaccountSlot,
|
|
195
|
+
SubaccountDeployRequest,
|
|
196
|
+
SubaccountSweepRequest,
|
|
197
|
+
SubaccountRelayResult,
|
|
198
|
+
SubaccountAssetBalance,
|
|
199
|
+
SubaccountBalances,
|
|
200
|
+
SubaccountQueueStatus,
|
|
201
|
+
SubaccountStatusResult,
|
|
202
|
+
SubaccountWithdrawTypedData,
|
|
203
|
+
SubaccountRecoveryResult,
|
|
169
204
|
} from './types.js';
|
package/src/relay.ts
CHANGED
|
@@ -50,6 +50,44 @@ export class RelayError extends Error {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export async function postRelayJson<T>(
|
|
54
|
+
endpoint: string,
|
|
55
|
+
body: unknown,
|
|
56
|
+
relayUrl?: string,
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
const url = relayUrl || getRelayUrl();
|
|
59
|
+
const response = await fetch(`${url}${endpoint}`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
let data: unknown;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(text);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new RelayError(
|
|
73
|
+
`Relay returned non-JSON response (HTTP ${response.status})`,
|
|
74
|
+
response.status,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const errorData = data as RelayErrorResponse;
|
|
80
|
+
throw new RelayError(
|
|
81
|
+
errorData.error || errorData.message || 'Relay request failed',
|
|
82
|
+
response.status,
|
|
83
|
+
errorData.retryAfter,
|
|
84
|
+
errorData.network,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return data as T;
|
|
89
|
+
}
|
|
90
|
+
|
|
53
91
|
/**
|
|
54
92
|
* Submit a withdrawal or transfer to the relay service
|
|
55
93
|
*
|
|
@@ -107,34 +145,17 @@ export async function submitRelay(options: SubmitRelayOptions): Promise<RelayRes
|
|
|
107
145
|
}
|
|
108
146
|
|
|
109
147
|
const relayUrl = customRelayUrl || getRelayUrl();
|
|
110
|
-
const endpoint =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
headers: {
|
|
115
|
-
'Content-Type': 'application/json',
|
|
116
|
-
},
|
|
117
|
-
body: JSON.stringify({
|
|
148
|
+
const endpoint = `/relay/${pool}`;
|
|
149
|
+
return postRelayJson<RelayResponse>(
|
|
150
|
+
endpoint,
|
|
151
|
+
{
|
|
118
152
|
type,
|
|
119
153
|
proofArgs,
|
|
120
154
|
extData,
|
|
121
155
|
metadata,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const data = await response.json();
|
|
126
|
-
|
|
127
|
-
if (!response.ok) {
|
|
128
|
-
const errorData = data as RelayErrorResponse;
|
|
129
|
-
throw new RelayError(
|
|
130
|
-
errorData.error || errorData.message || 'Relay request failed',
|
|
131
|
-
response.status,
|
|
132
|
-
errorData.retryAfter,
|
|
133
|
-
errorData.network
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return data as RelayResponse;
|
|
156
|
+
},
|
|
157
|
+
relayUrl,
|
|
158
|
+
);
|
|
138
159
|
}
|
|
139
160
|
|
|
140
161
|
/**
|