@ar.io/sdk 4.0.0-solana.1 → 4.0.0-solana.4
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/lib/cjs/cli/cli.js +72 -0
- package/lib/cjs/cli/commands/pruneCommands.js +179 -0
- package/lib/cjs/cli/options.js +29 -0
- package/lib/cjs/solana/ant-readable.js +1 -3
- package/lib/cjs/solana/deserialize.js +7 -4
- package/lib/cjs/solana/escrow.js +49 -4
- package/lib/cjs/solana/io-readable.js +260 -0
- package/lib/cjs/solana/io-writeable.js +185 -14
- package/lib/cjs/version.js +1 -1
- package/lib/esm/cli/cli.js +72 -0
- package/lib/esm/cli/commands/pruneCommands.js +166 -0
- package/lib/esm/cli/options.js +29 -0
- package/lib/esm/solana/ant-readable.js +1 -3
- package/lib/esm/solana/deserialize.js +7 -4
- package/lib/esm/solana/escrow.js +49 -4
- package/lib/esm/solana/io-readable.js +269 -9
- package/lib/esm/solana/io-writeable.js +188 -17
- package/lib/esm/version.js +1 -1
- package/lib/types/cli/commands/pruneCommands.d.ts +31 -0
- package/lib/types/cli/options.d.ts +26 -0
- package/lib/types/solana/escrow.d.ts +17 -0
- package/lib/types/solana/io-readable.d.ts +95 -0
- package/lib/types/solana/io-writeable.d.ts +79 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
package/lib/cjs/cli/cli.js
CHANGED
|
@@ -58,6 +58,7 @@ const antCommands_js_1 = require("./commands/antCommands.js");
|
|
|
58
58
|
const arnsPurchaseCommands_js_1 = require("./commands/arnsPurchaseCommands.js");
|
|
59
59
|
const escrowCommands_js_1 = require("./commands/escrowCommands.js");
|
|
60
60
|
const gatewayWriteCommands_js_1 = require("./commands/gatewayWriteCommands.js");
|
|
61
|
+
const pruneCommands_js_1 = require("./commands/pruneCommands.js");
|
|
61
62
|
const readCommands_js_1 = require("./commands/readCommands.js");
|
|
62
63
|
const transfer_js_1 = require("./commands/transfer.js");
|
|
63
64
|
const options_js_1 = require("./options.js");
|
|
@@ -491,6 +492,77 @@ const utils_js_1 = require("./utils.js");
|
|
|
491
492
|
options: options_js_1.arnsPurchaseOptions,
|
|
492
493
|
action: arnsPurchaseCommands_js_1.setPrimaryNameCLICommand,
|
|
493
494
|
});
|
|
495
|
+
// # Prune / cleanup (Solana-only — permissionless crank surface)
|
|
496
|
+
(0, utils_js_1.makeCommand)({
|
|
497
|
+
name: 'prune-expired-names',
|
|
498
|
+
description: 'Batch-prune expired ArnsRecord PDAs (Solana-only). Discovers eligible records ' +
|
|
499
|
+
'via getExpiredArnsRecords if --arns-records is omitted.',
|
|
500
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.max, options_js_1.optionMap.arnsRecords],
|
|
501
|
+
action: pruneCommands_js_1.pruneExpiredNamesCLICommand,
|
|
502
|
+
});
|
|
503
|
+
(0, utils_js_1.makeCommand)({
|
|
504
|
+
name: 'prune-name-to-returned',
|
|
505
|
+
description: 'Convert a single expired-but-not-yet-returned lease into a ReturnedName ' +
|
|
506
|
+
'(starts the Dutch auction). Solana-only.',
|
|
507
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.name],
|
|
508
|
+
action: pruneCommands_js_1.pruneNameToReturnedCLICommand,
|
|
509
|
+
});
|
|
510
|
+
(0, utils_js_1.makeCommand)({
|
|
511
|
+
name: 'prune-returned-names',
|
|
512
|
+
description: 'Batch-prune expired ReturnedName PDAs (Solana-only). Discovers via ' +
|
|
513
|
+
'getExpiredReturnedNames if --returned-names is omitted.',
|
|
514
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.max, options_js_1.optionMap.returnedNames],
|
|
515
|
+
action: pruneCommands_js_1.pruneReturnedNamesCLICommand,
|
|
516
|
+
});
|
|
517
|
+
(0, utils_js_1.makeCommand)({
|
|
518
|
+
name: 'prune-expired-reservation',
|
|
519
|
+
description: 'Close an expired ReservedName PDA (Solana-only).',
|
|
520
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.name],
|
|
521
|
+
action: pruneCommands_js_1.pruneExpiredReservationCLICommand,
|
|
522
|
+
});
|
|
523
|
+
(0, utils_js_1.makeCommand)({
|
|
524
|
+
name: 'prune-gateway',
|
|
525
|
+
description: 'Slash + remove a deficient gateway (≥30 consecutive failures). Solana-only.',
|
|
526
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.gateway],
|
|
527
|
+
action: pruneCommands_js_1.pruneGatewayCLICommand,
|
|
528
|
+
});
|
|
529
|
+
(0, utils_js_1.makeCommand)({
|
|
530
|
+
name: 'finalize-gone',
|
|
531
|
+
description: 'GC a Leaving/Gone gateway whose leave window has fully elapsed. Solana-only.',
|
|
532
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.gateway],
|
|
533
|
+
action: pruneCommands_js_1.finalizeGoneCLICommand,
|
|
534
|
+
});
|
|
535
|
+
(0, utils_js_1.makeCommand)({
|
|
536
|
+
name: 'close-observation',
|
|
537
|
+
description: 'Reclaim rent from an Observation PDA whose epoch has been distributed. Solana-only.',
|
|
538
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.epochIndex, options_js_1.optionMap.observer],
|
|
539
|
+
action: pruneCommands_js_1.closeObservationCLICommand,
|
|
540
|
+
});
|
|
541
|
+
(0, utils_js_1.makeCommand)({
|
|
542
|
+
name: 'close-empty-delegation',
|
|
543
|
+
description: 'Close an empty Delegation PDA (amount == 0). Rent refunds to the original delegator. Solana-only.',
|
|
544
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.gateway, options_js_1.optionMap.delegator],
|
|
545
|
+
action: pruneCommands_js_1.closeEmptyDelegationCLICommand,
|
|
546
|
+
});
|
|
547
|
+
(0, utils_js_1.makeCommand)({
|
|
548
|
+
name: 'close-drained-withdrawal',
|
|
549
|
+
description: 'Close a drained Withdrawal PDA (amount == 0). Rent refunds to the original owner. Solana-only.',
|
|
550
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.owner, options_js_1.optionMap.withdrawalId],
|
|
551
|
+
action: pruneCommands_js_1.closeDrainedWithdrawalCLICommand,
|
|
552
|
+
});
|
|
553
|
+
(0, utils_js_1.makeCommand)({
|
|
554
|
+
name: 'release-vault',
|
|
555
|
+
description: 'Release tokens from an expired vault back to the owner (Solana-only). ' +
|
|
556
|
+
'NOT permissionless — must be called from the vault owner wallet.',
|
|
557
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.vaultId, options_js_1.optionMap.owner],
|
|
558
|
+
action: pruneCommands_js_1.releaseVaultCLICommand,
|
|
559
|
+
});
|
|
560
|
+
(0, utils_js_1.makeCommand)({
|
|
561
|
+
name: 'close-expired-request',
|
|
562
|
+
description: 'Close an expired PrimaryNameRequest PDA. Solana-only.',
|
|
563
|
+
options: [...options_js_1.writeActionOptions, options_js_1.optionMap.initiator],
|
|
564
|
+
action: pruneCommands_js_1.closeExpiredRequestCLICommand,
|
|
565
|
+
});
|
|
494
566
|
// # ANT Registry
|
|
495
567
|
(0, utils_js_1.makeCommand)({
|
|
496
568
|
name: 'get-ants-for-address',
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pruneExpiredNamesCLICommand = pruneExpiredNamesCLICommand;
|
|
4
|
+
exports.pruneNameToReturnedCLICommand = pruneNameToReturnedCLICommand;
|
|
5
|
+
exports.pruneReturnedNamesCLICommand = pruneReturnedNamesCLICommand;
|
|
6
|
+
exports.pruneExpiredReservationCLICommand = pruneExpiredReservationCLICommand;
|
|
7
|
+
exports.pruneGatewayCLICommand = pruneGatewayCLICommand;
|
|
8
|
+
exports.finalizeGoneCLICommand = finalizeGoneCLICommand;
|
|
9
|
+
exports.closeObservationCLICommand = closeObservationCLICommand;
|
|
10
|
+
exports.closeEmptyDelegationCLICommand = closeEmptyDelegationCLICommand;
|
|
11
|
+
exports.closeDrainedWithdrawalCLICommand = closeDrainedWithdrawalCLICommand;
|
|
12
|
+
exports.releaseVaultCLICommand = releaseVaultCLICommand;
|
|
13
|
+
exports.closeExpiredRequestCLICommand = closeExpiredRequestCLICommand;
|
|
14
|
+
const utils_js_1 = require("../utils.js");
|
|
15
|
+
function rejectAoBackend(o, command) {
|
|
16
|
+
if (o.ao) {
|
|
17
|
+
throw new Error(`${command} is Solana-only — Lua's tick() handles the equivalent on AO. Drop --ao.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function getSolanaWriter(o) {
|
|
21
|
+
const { ario } = await (0, utils_js_1.writeARIOFromOptions)(o);
|
|
22
|
+
return ario;
|
|
23
|
+
}
|
|
24
|
+
function parseMaxNames(o) {
|
|
25
|
+
const raw = o.max;
|
|
26
|
+
if (raw === undefined) {
|
|
27
|
+
throw new Error('--max <count> is required (u8 batch size, 1-255)');
|
|
28
|
+
}
|
|
29
|
+
const n = Number(raw);
|
|
30
|
+
if (!Number.isInteger(n) || n < 1 || n > 255) {
|
|
31
|
+
throw new Error(`--max must be an integer 1-255 (got ${raw})`);
|
|
32
|
+
}
|
|
33
|
+
return n;
|
|
34
|
+
}
|
|
35
|
+
// =========================================
|
|
36
|
+
// ArNS prune
|
|
37
|
+
// =========================================
|
|
38
|
+
async function pruneExpiredNamesCLICommand(o) {
|
|
39
|
+
rejectAoBackend(o, 'prune-expired-names');
|
|
40
|
+
const max = parseMaxNames(o);
|
|
41
|
+
const ario = await getSolanaWriter(o);
|
|
42
|
+
// If `--arns-records` wasn't provided, discover them via the readable's
|
|
43
|
+
// helper. Cap at `max` so we never overshoot the ix's u8 batch parameter.
|
|
44
|
+
let records = o.arnsRecords ?? [];
|
|
45
|
+
if (records.length === 0) {
|
|
46
|
+
const now = Math.floor(Date.now() / 1000);
|
|
47
|
+
const expired = await ario.getExpiredArnsRecords(now);
|
|
48
|
+
records = expired.slice(0, max).map((r) => r.pubkey);
|
|
49
|
+
if (records.length === 0) {
|
|
50
|
+
return { message: 'No expired ArnsRecords to prune' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
records = records.slice(0, max);
|
|
55
|
+
}
|
|
56
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Prune ${records.length} expired ArnsRecord(s)?`, o);
|
|
57
|
+
return ario.pruneExpiredNames({ maxNames: max, arnsRecords: records });
|
|
58
|
+
}
|
|
59
|
+
async function pruneNameToReturnedCLICommand(o) {
|
|
60
|
+
rejectAoBackend(o, 'prune-name-to-returned');
|
|
61
|
+
const name = (0, utils_js_1.requiredStringFromOptions)(o, 'name');
|
|
62
|
+
const ario = await getSolanaWriter(o);
|
|
63
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Convert expired lease for "${name}" to a ReturnedName (Dutch auction)?`, o);
|
|
64
|
+
return ario.pruneNameToReturned({ name });
|
|
65
|
+
}
|
|
66
|
+
async function pruneReturnedNamesCLICommand(o) {
|
|
67
|
+
rejectAoBackend(o, 'prune-returned-names');
|
|
68
|
+
const max = parseMaxNames(o);
|
|
69
|
+
const ario = await getSolanaWriter(o);
|
|
70
|
+
let returned = o.returnedNames ?? [];
|
|
71
|
+
if (returned.length === 0) {
|
|
72
|
+
const now = Math.floor(Date.now() / 1000);
|
|
73
|
+
const expired = await ario.getExpiredReturnedNames(now);
|
|
74
|
+
returned = expired.slice(0, max).map((r) => r.pubkey);
|
|
75
|
+
if (returned.length === 0) {
|
|
76
|
+
return { message: 'No expired ReturnedNames to prune' };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
returned = returned.slice(0, max);
|
|
81
|
+
}
|
|
82
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Prune ${returned.length} expired ReturnedName(s)?`, o);
|
|
83
|
+
return ario.pruneReturnedNames({
|
|
84
|
+
maxNames: max,
|
|
85
|
+
returnedNames: returned,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function pruneExpiredReservationCLICommand(o) {
|
|
89
|
+
rejectAoBackend(o, 'prune-expired-reservation');
|
|
90
|
+
const name = (0, utils_js_1.requiredStringFromOptions)(o, 'name');
|
|
91
|
+
const ario = await getSolanaWriter(o);
|
|
92
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Close expired reservation for "${name}"?`, o);
|
|
93
|
+
return ario.pruneExpiredReservation({ name });
|
|
94
|
+
}
|
|
95
|
+
// =========================================
|
|
96
|
+
// Gateway prune
|
|
97
|
+
// =========================================
|
|
98
|
+
async function pruneGatewayCLICommand(o) {
|
|
99
|
+
rejectAoBackend(o, 'prune-gateway');
|
|
100
|
+
const gateway = (0, utils_js_1.requiredStringFromOptions)(o, 'gateway');
|
|
101
|
+
const ario = await getSolanaWriter(o);
|
|
102
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Slash + remove deficient gateway ${gateway}?`, o);
|
|
103
|
+
return ario.pruneGateway({ gateway });
|
|
104
|
+
}
|
|
105
|
+
async function finalizeGoneCLICommand(o) {
|
|
106
|
+
rejectAoBackend(o, 'finalize-gone');
|
|
107
|
+
const gateway = (0, utils_js_1.requiredStringFromOptions)(o, 'gateway');
|
|
108
|
+
const ario = await getSolanaWriter(o);
|
|
109
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Finalize-GC departed gateway ${gateway} (reclaim PDA rent)?`, o);
|
|
110
|
+
return ario.finalizeGone({ gateway });
|
|
111
|
+
}
|
|
112
|
+
// =========================================
|
|
113
|
+
// Rent reclaim
|
|
114
|
+
// =========================================
|
|
115
|
+
async function closeObservationCLICommand(o) {
|
|
116
|
+
rejectAoBackend(o, 'close-observation');
|
|
117
|
+
const epochIndexStr = (0, utils_js_1.requiredStringFromOptions)(o, 'epochIndex');
|
|
118
|
+
const observer = (0, utils_js_1.requiredStringFromOptions)(o, 'observer');
|
|
119
|
+
const epochIndex = Number(epochIndexStr);
|
|
120
|
+
if (!Number.isInteger(epochIndex) || epochIndex < 0) {
|
|
121
|
+
throw new Error(`--epoch-index must be a non-negative integer (got ${epochIndexStr})`);
|
|
122
|
+
}
|
|
123
|
+
const ario = await getSolanaWriter(o);
|
|
124
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Close Observation PDA (epoch ${epochIndex}, observer ${observer})?`, o);
|
|
125
|
+
return ario.closeObservation({ epochIndex, observer });
|
|
126
|
+
}
|
|
127
|
+
async function closeEmptyDelegationCLICommand(o) {
|
|
128
|
+
rejectAoBackend(o, 'close-empty-delegation');
|
|
129
|
+
const gateway = (0, utils_js_1.requiredStringFromOptions)(o, 'gateway');
|
|
130
|
+
const delegator = (0, utils_js_1.requiredStringFromOptions)(o, 'delegator');
|
|
131
|
+
const ario = await getSolanaWriter(o);
|
|
132
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Close empty Delegation PDA (gateway=${gateway}, delegator=${delegator})?`, o);
|
|
133
|
+
return ario.closeEmptyDelegation({ gateway, delegator });
|
|
134
|
+
}
|
|
135
|
+
async function closeDrainedWithdrawalCLICommand(o) {
|
|
136
|
+
rejectAoBackend(o, 'close-drained-withdrawal');
|
|
137
|
+
const owner = (0, utils_js_1.requiredStringFromOptions)(o, 'owner');
|
|
138
|
+
const withdrawalIdStr = (0, utils_js_1.requiredStringFromOptions)(o, 'withdrawalId');
|
|
139
|
+
// Validate before BigInt() — `BigInt('0xff')` and `BigInt(' 1 ')` succeed
|
|
140
|
+
// and `BigInt('abc')` throws an opaque SyntaxError. Restrict to the u64
|
|
141
|
+
// decimal form the on-chain seed encoder expects so the CLI fails with a
|
|
142
|
+
// clear message instead of a downstream parser error.
|
|
143
|
+
if (!/^\d+$/.test(withdrawalIdStr)) {
|
|
144
|
+
throw new Error(`--withdrawal-id must be a non-negative decimal integer (got "${withdrawalIdStr}")`);
|
|
145
|
+
}
|
|
146
|
+
const withdrawalId = BigInt(withdrawalIdStr);
|
|
147
|
+
const ario = await getSolanaWriter(o);
|
|
148
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Close drained Withdrawal PDA (owner=${owner}, id=${withdrawalIdStr})?`, o);
|
|
149
|
+
return ario.closeDrainedWithdrawal({ owner, withdrawalId });
|
|
150
|
+
}
|
|
151
|
+
// =========================================
|
|
152
|
+
// Vault + primary-name request
|
|
153
|
+
// =========================================
|
|
154
|
+
async function releaseVaultCLICommand(o) {
|
|
155
|
+
rejectAoBackend(o, 'release-vault');
|
|
156
|
+
// The on-chain handler requires `owner: Signer` — the SDK uses the
|
|
157
|
+
// configured signer as the owner. The `--owner` flag is accepted as
|
|
158
|
+
// documentation but ignored unless it matches the signer; fail loud if
|
|
159
|
+
// it doesn't, so users don't think they can release someone else's vault.
|
|
160
|
+
const ario = await getSolanaWriter(o);
|
|
161
|
+
if (o.owner) {
|
|
162
|
+
const signerAddr = (await (0, utils_js_1.writeARIOFromOptions)(o)).signerAddress;
|
|
163
|
+
if (o.owner !== signerAddr) {
|
|
164
|
+
throw new Error(`release-vault: --owner ${o.owner} does not match signer ${signerAddr}. ` +
|
|
165
|
+
`release_vault is owner-signed; use the owner's wallet to call it.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const vaultIdStr = (0, utils_js_1.requiredStringFromOptions)(o, 'vaultId');
|
|
169
|
+
const vaultId = vaultIdStr;
|
|
170
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Release expired vault id=${vaultIdStr} (transfer tokens back to owner)?`, o);
|
|
171
|
+
return ario.releaseVault({ vaultId });
|
|
172
|
+
}
|
|
173
|
+
async function closeExpiredRequestCLICommand(o) {
|
|
174
|
+
rejectAoBackend(o, 'close-expired-request');
|
|
175
|
+
const initiator = (0, utils_js_1.requiredStringFromOptions)(o, 'initiator');
|
|
176
|
+
const ario = await getSolanaWriter(o);
|
|
177
|
+
await (0, utils_js_1.assertConfirmationPrompt)(`Close expired primary-name request from ${initiator}?`, o);
|
|
178
|
+
return ario.closeExpiredRequest({ initiator });
|
|
179
|
+
}
|
package/lib/cjs/cli/options.js
CHANGED
|
@@ -424,6 +424,35 @@ exports.optionMap = {
|
|
|
424
424
|
description: 'Reassign all affiliated names to the new process',
|
|
425
425
|
type: 'boolean',
|
|
426
426
|
},
|
|
427
|
+
// -----------------------------------------------------------------
|
|
428
|
+
// Prune / cleanup flags (Solana-only — see pruneCommands.ts)
|
|
429
|
+
// -----------------------------------------------------------------
|
|
430
|
+
gateway: {
|
|
431
|
+
alias: '--gateway <gateway>',
|
|
432
|
+
description: 'The gateway operator address (prune / finalize commands)',
|
|
433
|
+
},
|
|
434
|
+
delegator: {
|
|
435
|
+
alias: '--delegator <delegator>',
|
|
436
|
+
description: 'The delegator address (close-empty-delegation)',
|
|
437
|
+
},
|
|
438
|
+
observer: {
|
|
439
|
+
alias: '--observer <observer>',
|
|
440
|
+
description: 'The observer address (close-observation)',
|
|
441
|
+
},
|
|
442
|
+
max: {
|
|
443
|
+
alias: '--max <max>',
|
|
444
|
+
description: 'Per-tx batch size (1-255, u8) for prune-expired-names / prune-returned-names',
|
|
445
|
+
},
|
|
446
|
+
arnsRecords: {
|
|
447
|
+
alias: '--arns-records <arnsRecords...>',
|
|
448
|
+
description: 'Explicit ArnsRecord PDAs to prune. Default: discover via getExpiredArnsRecords.',
|
|
449
|
+
type: 'array',
|
|
450
|
+
},
|
|
451
|
+
returnedNames: {
|
|
452
|
+
alias: '--returned-names <returnedNames...>',
|
|
453
|
+
description: 'Explicit ReturnedName PDAs to prune. Default: discover via getExpiredReturnedNames.',
|
|
454
|
+
type: 'array',
|
|
455
|
+
},
|
|
427
456
|
};
|
|
428
457
|
exports.walletOptions = [
|
|
429
458
|
exports.optionMap.walletFile,
|
|
@@ -277,9 +277,7 @@ class SolanaANTReadable {
|
|
|
277
277
|
try {
|
|
278
278
|
const buf = Buffer.from(account.data[0], 'base64');
|
|
279
279
|
const record = (0, deserialize_js_1.deserializeAntRecord)(buf);
|
|
280
|
-
|
|
281
|
-
const { createHash } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
282
|
-
const hash = createHash('sha256')
|
|
280
|
+
const hash = (0, crypto_1.createHash)('sha256')
|
|
283
281
|
.update(record.undername.toLowerCase())
|
|
284
282
|
.digest('hex');
|
|
285
283
|
const meta = metaByHash.get(hash) ?? {};
|
|
@@ -330,6 +330,9 @@ function deserializeGateway(data) {
|
|
|
330
330
|
const statusIdx = r.readU8(); // 0=Joined, 1=Leaving
|
|
331
331
|
const startTimestamp = r.readI64AsNumber();
|
|
332
332
|
const leaveTimestamp = r.readOptionI64();
|
|
333
|
+
// leave_epoch_duration: i64 — snapshot of epoch_settings.epoch_duration captured
|
|
334
|
+
// at leave_network/prune_gateway. Not surfaced on AoGateway; consume to stay aligned.
|
|
335
|
+
r.skip(8);
|
|
333
336
|
// GatewayStats
|
|
334
337
|
const passedEpochCount = r.readU32();
|
|
335
338
|
const failedEpochCount = r.readU32();
|
|
@@ -338,17 +341,17 @@ function deserializeGateway(data) {
|
|
|
338
341
|
const observedEpochCount = r.readU32();
|
|
339
342
|
const failedConsecutiveEpochs = r.readU8();
|
|
340
343
|
const passedConsecutiveEpochs = r.readU8();
|
|
341
|
-
// GatewayWeights (
|
|
344
|
+
// GatewayWeights (7 x u64 — 7th is weights_epoch, set by tally_weights)
|
|
342
345
|
const stakeWeight = r.readU64AsNumber();
|
|
343
346
|
const tenureWeight = r.readU64AsNumber();
|
|
344
347
|
const gatewayPerformanceRatio = r.readU64AsNumber();
|
|
345
348
|
const observerPerformanceRatio = r.readU64AsNumber();
|
|
346
349
|
const compositeWeight = r.readU64AsNumber();
|
|
347
350
|
const normalizedCompositeWeight = r.readU64AsNumber();
|
|
348
|
-
//
|
|
351
|
+
r.skip(8); // weights_epoch — not surfaced on AoGatewayWeights
|
|
352
|
+
// GatewaySettings2 (auto_stake removed in cfc7a8b2 — never existed on Solana)
|
|
349
353
|
const allowDelegatedStaking = r.readBool();
|
|
350
354
|
const delegateRewardShareRatio = r.readU16();
|
|
351
|
-
const autoStake = r.readBool();
|
|
352
355
|
const minDelegatedStake = r.readU64AsNumber();
|
|
353
356
|
const allowlistEnabled = r.readBool();
|
|
354
357
|
// RegistryIndex (index: u32, _reserved: u8 — was is_registered:bool)
|
|
@@ -386,7 +389,7 @@ function deserializeGateway(data) {
|
|
|
386
389
|
delegateRewardShareRatio,
|
|
387
390
|
allowedDelegates: [], // populated separately from allowlist PDAs
|
|
388
391
|
minDelegatedStake,
|
|
389
|
-
autoStake,
|
|
392
|
+
autoStake: false, // not an on-chain field on Solana; preserved on AoGatewaySettings for AO parity
|
|
390
393
|
label,
|
|
391
394
|
note,
|
|
392
395
|
properties,
|
package/lib/cjs/solana/escrow.js
CHANGED
|
@@ -463,8 +463,12 @@ class TokenEscrow {
|
|
|
463
463
|
saltLen: args.saltLen ?? 32,
|
|
464
464
|
messageNonce: escrow.nonce,
|
|
465
465
|
});
|
|
466
|
+
// The on-chain claim handler delivers liquid tokens to
|
|
467
|
+
// `claimantTokenAccount`; for fresh-wallet claimants the canonical ATA
|
|
468
|
+
// doesn't exist yet (#3012). Idempotent-create when canonical.
|
|
469
|
+
const createAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
|
|
466
470
|
// RSA-PSS-4096 verification is CU-intensive; use 400K.
|
|
467
|
-
return this.send([ix], 400_000);
|
|
471
|
+
return this.send(createAtaIx ? [createAtaIx, ix] : [ix], 400_000);
|
|
468
472
|
}
|
|
469
473
|
async claimTokensArweaveIx(args) {
|
|
470
474
|
if (args.signature.length !== 512) {
|
|
@@ -500,7 +504,10 @@ class TokenEscrow {
|
|
|
500
504
|
...args,
|
|
501
505
|
messageNonce: escrow.nonce,
|
|
502
506
|
});
|
|
503
|
-
|
|
507
|
+
// Same fresh-wallet #3012 vector as claimTokensArweave — bundle a
|
|
508
|
+
// canonical-ATA idempotent-create when applicable.
|
|
509
|
+
const createAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
|
|
510
|
+
return this.send(createAtaIx ? [createAtaIx, ix] : [ix]);
|
|
504
511
|
}
|
|
505
512
|
async claimTokensEthereumIx(args) {
|
|
506
513
|
if (args.signature.length !== 65) {
|
|
@@ -604,16 +611,53 @@ class TokenEscrow {
|
|
|
604
611
|
* (see the introspection function's tolerance), so any modest clock skew
|
|
605
612
|
* between client and chain is absorbed.
|
|
606
613
|
*/
|
|
614
|
+
/**
|
|
615
|
+
* Idempotent-create the claimant's canonical ATA when needed.
|
|
616
|
+
*
|
|
617
|
+
* The on-chain claim handler delivers liquid tokens directly to
|
|
618
|
+
* `claimantTokenAccount` for expired vaults AND for token escrows.
|
|
619
|
+
* For active vaults, the `claim_vault_*` Anchor Accounts struct still
|
|
620
|
+
* declares `claimant_token_account: Account<TokenAccount>`, which forces
|
|
621
|
+
* Anchor's account-load-time validation to require the account exist
|
|
622
|
+
* even though the active path doesn't write to it. Either way: if the
|
|
623
|
+
* claimant is a fresh wallet that has never held this mint, the ATA
|
|
624
|
+
* doesn't exist and the tx fails with `AccountNotInitialized` (#3012).
|
|
625
|
+
*
|
|
626
|
+
* Returns `null` when the caller passed a non-canonical
|
|
627
|
+
* `claimantTokenAccount` (manually-created non-ATA token account,
|
|
628
|
+
* presumably already exists — caller's responsibility).
|
|
629
|
+
*/
|
|
630
|
+
async _createClaimantAtaIfCanonical(claimant, claimantTokenAccount, mint) {
|
|
631
|
+
const canonical = await (0, ata_js_1.getAssociatedTokenAddressKit)(mint, claimant);
|
|
632
|
+
if (claimantTokenAccount !== canonical)
|
|
633
|
+
return null;
|
|
634
|
+
const signer = this.requireSigner('createClaimantAtaIfCanonical');
|
|
635
|
+
return buildCreateAtaIdempotentIx(signer.address, canonical, claimant, mint);
|
|
636
|
+
}
|
|
607
637
|
async maybeBundleVaultedTransfer(escrow, args, claimIx) {
|
|
608
638
|
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
|
|
609
639
|
const remaining = escrow.vaultEndTimestamp - nowSeconds;
|
|
610
640
|
if (remaining <= 0n) {
|
|
611
|
-
|
|
641
|
+
// Expired vault → claim handler delivers liquid to claimantTokenAccount.
|
|
642
|
+
// Idempotent-create that ATA if it's the canonical derivation so a
|
|
643
|
+
// first-time recipient just works.
|
|
644
|
+
const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
|
|
645
|
+
return createClaimantAtaIx ? [createClaimantAtaIx, claimIx] : [claimIx];
|
|
612
646
|
}
|
|
613
647
|
const signer = this.requireSigner('maybeBundleVaultedTransfer');
|
|
614
648
|
const nextId = await this.getNextVaultId(args.claimant);
|
|
615
649
|
const [vaultPda] = await (0, pda_js_1.getVaultPDA)(args.claimant, nextId, this.coreProgram);
|
|
616
650
|
const vaultATA = await (0, ata_js_1.getAssociatedTokenAddressKit)(escrow.arioMint, vaultPda, true);
|
|
651
|
+
// Active-vault path: `claim_vault_*` still validates the claimant ATA
|
|
652
|
+
// at account-load-time (Anchor `Account<TokenAccount>` constraint),
|
|
653
|
+
// even though no liquid is written to it. Idempotent-create so a fresh
|
|
654
|
+
// claimant doesn't fail the ix with AccountNotInitialized (#3012).
|
|
655
|
+
const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
|
|
656
|
+
// The new vault PDA's ATA must exist before `vaulted_transfer` reads it
|
|
657
|
+
// (else `AccountNotInitialized` #3012). Idempotent so a retry after a
|
|
658
|
+
// partial-failure tx is safe. Placed after the claim ix to preserve
|
|
659
|
+
// the "claim first" tx ordering invariant.
|
|
660
|
+
const createVaultAtaIx = buildCreateAtaIdempotentIx(signer.address, vaultATA, vaultPda, escrow.arioMint);
|
|
617
661
|
const vaultedIx = await (0, vaultedTransfer_js_1.getVaultedTransferInstructionAsync)({
|
|
618
662
|
vault: vaultPda,
|
|
619
663
|
senderTokenAccount: args.payerTokenAccount,
|
|
@@ -624,7 +668,8 @@ class TokenEscrow {
|
|
|
624
668
|
lockDurationSeconds: remaining,
|
|
625
669
|
revocable: escrow.vaultRevocable,
|
|
626
670
|
}, { programAddress: this.coreProgram });
|
|
627
|
-
|
|
671
|
+
const head = createClaimantAtaIx ? [createClaimantAtaIx] : [];
|
|
672
|
+
return [...head, claimIx, createVaultAtaIx, vaultedIx];
|
|
628
673
|
}
|
|
629
674
|
/** Read the recipient's `VaultCounter.nextId`, defaulting to 0n if the
|
|
630
675
|
* counter PDA hasn't been initialised yet (first vault for that owner). */
|