@ar.io/sdk 4.0.0-solana.26 → 4.0.0-solana.28
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/esm/solana/io-readable.js +78 -5
- package/lib/esm/solana/io-writeable.js +54 -1
- package/lib/esm/version.js +1 -1
- package/lib/types/solana/io-readable.d.ts +33 -2
- package/lib/types/solana/io-writeable.d.ts +23 -0
- package/lib/types/types/io.d.ts +10 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -290,9 +290,23 @@ export class SolanaARIOReadable {
|
|
|
290
290
|
let staked = 0;
|
|
291
291
|
let delegated = 0;
|
|
292
292
|
let withdrawn = 0;
|
|
293
|
+
// `protocolBalance` is the REWARD RESERVE: the live balance of the protocol
|
|
294
|
+
// token account the epoch cranker emits from (ario-gar `distribute_epoch`
|
|
295
|
+
// reads `protocol_token_account.amount`). This matches AO's `protocolBalance`
|
|
296
|
+
// semantics (the qNvAoz0 reserve) and makes the six buckets sum to `total`:
|
|
297
|
+
// circulating + locked + staked + delegated + withdrawn + protocolBalance == total
|
|
298
|
+
//
|
|
299
|
+
// It is deliberately NOT `ArioConfig.protocol_balance`, which is a *folded*
|
|
300
|
+
// accounting field (= reserve + staked + delegated + withdrawn) and so
|
|
301
|
+
// double-counts the staking buckets — surfacing it as "protocol balance"
|
|
302
|
+
// over-reports the reward reserve by tens of millions of ARIO. We fall back
|
|
303
|
+
// to the folded value only when GatewaySettings or the token account can't
|
|
304
|
+
// be read (e.g. pre-init).
|
|
305
|
+
let protocolBalance = config.protocolBalance;
|
|
293
306
|
if (garSettingsAccount.exists) {
|
|
307
|
+
const garData = Buffer.from(garSettingsAccount.data);
|
|
294
308
|
try {
|
|
295
|
-
const counters = deserializeGarSupplyCounters(
|
|
309
|
+
const counters = deserializeGarSupplyCounters(garData);
|
|
296
310
|
staked = counters.totalStaked;
|
|
297
311
|
delegated = counters.totalDelegated;
|
|
298
312
|
withdrawn = counters.totalWithdrawn;
|
|
@@ -300,6 +314,22 @@ export class SolanaARIOReadable {
|
|
|
300
314
|
catch {
|
|
301
315
|
// Old-layout account without supply counters — fall back to 0
|
|
302
316
|
}
|
|
317
|
+
// The protocol token account is pinned in GatewaySettings at offset 189
|
|
318
|
+
// (see ario-gar state/mod.rs::GatewaySettings: 8 disc + 32 authority +
|
|
319
|
+
// 32 mint + 6×8 economic params + 4 max_delegates + 1 migration_active +
|
|
320
|
+
// 32 migration_authority + 32 stake_token_account = 189; the pubkey runs
|
|
321
|
+
// [189, 221)). Read its live SPL balance as the reward reserve.
|
|
322
|
+
if (garData.length >= 221) {
|
|
323
|
+
try {
|
|
324
|
+
const protocolTokenAccount = addressDecoder.decode(garData.subarray(189, 221));
|
|
325
|
+
const reserve = await this.getTokenAccountAmount(protocolTokenAccount);
|
|
326
|
+
if (reserve !== null)
|
|
327
|
+
protocolBalance = reserve;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// Couldn't read the reserve — keep the folded fallback.
|
|
331
|
+
}
|
|
332
|
+
}
|
|
303
333
|
}
|
|
304
334
|
return {
|
|
305
335
|
total: config.totalSupply,
|
|
@@ -308,9 +338,31 @@ export class SolanaARIOReadable {
|
|
|
308
338
|
staked,
|
|
309
339
|
delegated,
|
|
310
340
|
withdrawn,
|
|
311
|
-
protocolBalance
|
|
341
|
+
protocolBalance,
|
|
312
342
|
};
|
|
313
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Read the `amount` (in mARIO) of an SPL token account directly by address.
|
|
346
|
+
* Returns `null` if the account doesn't exist or is too small to be a token
|
|
347
|
+
* account, so callers can distinguish "absent" from a real zero balance.
|
|
348
|
+
*
|
|
349
|
+
* Uses a portable little-endian u64 decode — some browser bundlers strip the
|
|
350
|
+
* BigInt readers from the `buffer` shim's prototype (see `getBalance`).
|
|
351
|
+
*/
|
|
352
|
+
async getTokenAccountAmount(tokenAccount) {
|
|
353
|
+
const account = await this.getAccount(tokenAccount);
|
|
354
|
+
if (!account.exists)
|
|
355
|
+
return null;
|
|
356
|
+
const data = account.data;
|
|
357
|
+
if (data.length < 72)
|
|
358
|
+
return null;
|
|
359
|
+
let amount = 0n;
|
|
360
|
+
for (let i = 7; i >= 0; i--) {
|
|
361
|
+
amount = (amount << 8n) | BigInt(data[64 + i]);
|
|
362
|
+
}
|
|
363
|
+
// ARIO supply caps at 1B * 1e6 mARIO ≈ 2^50, well under MAX_SAFE_INTEGER.
|
|
364
|
+
return Number(amount);
|
|
365
|
+
}
|
|
314
366
|
// =========================================
|
|
315
367
|
// Balance read methods
|
|
316
368
|
// =========================================
|
|
@@ -1723,8 +1775,21 @@ export class SolanaARIOReadable {
|
|
|
1723
1775
|
return out;
|
|
1724
1776
|
}
|
|
1725
1777
|
/**
|
|
1726
|
-
* Enumerate Gateway PDAs
|
|
1727
|
-
*
|
|
1778
|
+
* Enumerate Gateway PDAs that are candidates for `finalizeGone` GC — i.e.
|
|
1779
|
+
* those whose `status == Leaving`.
|
|
1780
|
+
*
|
|
1781
|
+
* NOTE: despite the historical name, this returns `Leaving` (not `Gone`)
|
|
1782
|
+
* gateways. `Gone` is NOT a persistent discovery state: the on-chain
|
|
1783
|
+
* `finalize_gone` instruction accepts a `Leaving` gateway, flips it to
|
|
1784
|
+
* `Gone`, and closes the Gateway PDA in the *same* instruction
|
|
1785
|
+
* (programs/ario-gar/src/instructions/gateway.rs::finalize_gone), so a
|
|
1786
|
+
* gateway is never observably parked at `Gone`. Filtering on `Gone` matched
|
|
1787
|
+
* nothing and left Leaving gateways un-GC'd. Time/delegation eligibility is
|
|
1788
|
+
* still enforced on-chain (`finalize_gone` reverts early if the leave window
|
|
1789
|
+
* hasn't elapsed or delegations remain), so over-returning not-yet-eligible
|
|
1790
|
+
* Leaving gateways is safe — the tx just no-ops/reverts.
|
|
1791
|
+
*
|
|
1792
|
+
* @deprecated Prefer {@link getLeavingGateways} — same result, accurate name.
|
|
1728
1793
|
*/
|
|
1729
1794
|
async getGoneGateways() {
|
|
1730
1795
|
const accounts = await this.getAccountsByDiscriminator(this.garProgram, GATEWAY_DISCRIMINATOR);
|
|
@@ -1733,7 +1798,7 @@ export class SolanaARIOReadable {
|
|
|
1733
1798
|
for (const { pubkey, data } of accounts) {
|
|
1734
1799
|
try {
|
|
1735
1800
|
const g = decoder.decode(data);
|
|
1736
|
-
if (g.status === GatewayStatus.
|
|
1801
|
+
if (g.status === GatewayStatus.Leaving) {
|
|
1737
1802
|
out.push({ pubkey, operator: g.operator });
|
|
1738
1803
|
}
|
|
1739
1804
|
}
|
|
@@ -1743,6 +1808,14 @@ export class SolanaARIOReadable {
|
|
|
1743
1808
|
}
|
|
1744
1809
|
return out;
|
|
1745
1810
|
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Enumerate Gateway PDAs whose `status == Leaving` — the persistent
|
|
1813
|
+
* pre-finalization state that `finalizeGone` GC's. Alias for
|
|
1814
|
+
* {@link getGoneGateways} with a name that matches the on-chain state.
|
|
1815
|
+
*/
|
|
1816
|
+
async getLeavingGateways() {
|
|
1817
|
+
return this.getGoneGateways();
|
|
1818
|
+
}
|
|
1746
1819
|
/**
|
|
1747
1820
|
* Enumerate Joined Gateway PDAs whose delegation has been DISABLED
|
|
1748
1821
|
* (`allow_delegated_staking == false`) yet still hold delegated stake
|
|
@@ -69,6 +69,36 @@ function withRemainingAccounts(ix, remaining) {
|
|
|
69
69
|
];
|
|
70
70
|
return { ...ix, accounts };
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Pick the swapped-gateway operator that `finalize_gone` needs as a writable
|
|
74
|
+
* `remaining_accounts[0]`.
|
|
75
|
+
*
|
|
76
|
+
* `finalize_gone` reclaims a gateway's slot from the compact GatewayRegistry by
|
|
77
|
+
* moving the LAST active slot into it and rewriting that swapped gateway's
|
|
78
|
+
* stored `registry_index`. When the finalized gateway is NOT already the last
|
|
79
|
+
* slot, the on-chain handler requires the swapped Gateway PDA (writable) at
|
|
80
|
+
* `remaining_accounts[0]`; when it IS the last slot, no swap occurs and no
|
|
81
|
+
* extra account is needed. See
|
|
82
|
+
* `programs/ario-gar/src/instructions/gateway.rs::finalize_gone`.
|
|
83
|
+
*
|
|
84
|
+
* `registryAddresses` MUST be the active registry operator addresses in slot
|
|
85
|
+
* order (`getRegistryGatewayAddresses()` — length === on-chain
|
|
86
|
+
* `registry.count`), so `registryAddresses[length - 1]` is exactly the
|
|
87
|
+
* `registry.gateways[count - 1].address` the on-chain swap reads.
|
|
88
|
+
*
|
|
89
|
+
* @returns the swapped gateway's operator address, or `null` when the finalized
|
|
90
|
+
* gateway already occupies the last slot.
|
|
91
|
+
* @throws if `registryIndex` is outside the active registry count (mirrors the
|
|
92
|
+
* on-chain `index < registry.count` guard, surfacing a stale index early).
|
|
93
|
+
*/
|
|
94
|
+
export function selectFinalizeGoneSwapOperator(registryIndex, registryAddresses) {
|
|
95
|
+
if (registryIndex < 0 || registryIndex >= registryAddresses.length) {
|
|
96
|
+
throw new Error(`finalizeGone: registry index ${registryIndex} is outside the active ` +
|
|
97
|
+
`registry count ${registryAddresses.length}`);
|
|
98
|
+
}
|
|
99
|
+
const lastIndex = registryAddresses.length - 1;
|
|
100
|
+
return registryIndex === lastIndex ? null : registryAddresses[lastIndex];
|
|
101
|
+
}
|
|
72
102
|
/**
|
|
73
103
|
* Split a primary name into its undername + base parts using the same rule
|
|
74
104
|
* as the on-chain `splitn(2, '_')` in `programs/ario-core/src/instructions/primary_name.rs`:
|
|
@@ -2627,11 +2657,34 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2627
2657
|
async finalizeGone(params, _options) {
|
|
2628
2658
|
const gatewayAddr = address(params.gateway);
|
|
2629
2659
|
const [gatewayPda] = await getGatewayPDA(gatewayAddr, this.garProgram);
|
|
2660
|
+
// `finalize_gone` reclaims this gateway's compact-registry slot by
|
|
2661
|
+
// swap-removing the LAST active slot into it. When this gateway is not the
|
|
2662
|
+
// last slot, the on-chain handler rewrites the swapped gateway's stored
|
|
2663
|
+
// registry_index and therefore requires that swapped Gateway PDA as
|
|
2664
|
+
// writable remaining_accounts[0]
|
|
2665
|
+
// (programs/ario-gar/src/instructions/gateway.rs::finalize_gone). Read the
|
|
2666
|
+
// gateway's slot index + the active registry to decide.
|
|
2667
|
+
const gatewayAccount = await fetchEncodedAccount(this.rpc, gatewayPda, {
|
|
2668
|
+
commitment: this.commitment,
|
|
2669
|
+
});
|
|
2670
|
+
if (!gatewayAccount.exists) {
|
|
2671
|
+
throw new Error(`Gateway not found for operator ${params.gateway}`);
|
|
2672
|
+
}
|
|
2673
|
+
const gateway = getGatewayDecoder().decode(gatewayAccount.data);
|
|
2674
|
+
const registryAddresses = await this.getRegistryGatewayAddresses();
|
|
2675
|
+
const swappedOperator = selectFinalizeGoneSwapOperator(gateway.registryIndex.index, registryAddresses);
|
|
2630
2676
|
const ix = await getFinalizeGoneInstructionAsync(await this.withGarDefaults({
|
|
2631
2677
|
gateway: gatewayPda,
|
|
2632
2678
|
caller: this.signer,
|
|
2633
2679
|
}), { programAddress: this.garProgram });
|
|
2634
|
-
|
|
2680
|
+
let finalIx = ix;
|
|
2681
|
+
if (swappedOperator !== null) {
|
|
2682
|
+
const [swappedGatewayPda] = await getGatewayPDA(address(swappedOperator), this.garProgram);
|
|
2683
|
+
finalIx = withRemainingAccounts(ix, [
|
|
2684
|
+
{ address: swappedGatewayPda, role: AccountRole.WRITABLE },
|
|
2685
|
+
]);
|
|
2686
|
+
}
|
|
2687
|
+
const sig = await this.sendTransaction([finalIx]);
|
|
2635
2688
|
return { id: sig };
|
|
2636
2689
|
}
|
|
2637
2690
|
/**
|
package/lib/esm/version.js
CHANGED
|
@@ -83,6 +83,15 @@ export declare class SolanaARIOReadable {
|
|
|
83
83
|
LastDistributedEpochIndex: number;
|
|
84
84
|
}>;
|
|
85
85
|
getTokenSupply(): Promise<TokenSupplyData>;
|
|
86
|
+
/**
|
|
87
|
+
* Read the `amount` (in mARIO) of an SPL token account directly by address.
|
|
88
|
+
* Returns `null` if the account doesn't exist or is too small to be a token
|
|
89
|
+
* account, so callers can distinguish "absent" from a real zero balance.
|
|
90
|
+
*
|
|
91
|
+
* Uses a portable little-endian u64 decode — some browser bundlers strip the
|
|
92
|
+
* BigInt readers from the `buffer` shim's prototype (see `getBalance`).
|
|
93
|
+
*/
|
|
94
|
+
private getTokenAccountAmount;
|
|
86
95
|
/**
|
|
87
96
|
* Resolve the ARIO SPL mint address from the on-chain `ArioConfig`.
|
|
88
97
|
*
|
|
@@ -302,13 +311,35 @@ export declare class SolanaARIOReadable {
|
|
|
302
311
|
failedConsecutive: number;
|
|
303
312
|
}>>;
|
|
304
313
|
/**
|
|
305
|
-
* Enumerate Gateway PDAs
|
|
306
|
-
*
|
|
314
|
+
* Enumerate Gateway PDAs that are candidates for `finalizeGone` GC — i.e.
|
|
315
|
+
* those whose `status == Leaving`.
|
|
316
|
+
*
|
|
317
|
+
* NOTE: despite the historical name, this returns `Leaving` (not `Gone`)
|
|
318
|
+
* gateways. `Gone` is NOT a persistent discovery state: the on-chain
|
|
319
|
+
* `finalize_gone` instruction accepts a `Leaving` gateway, flips it to
|
|
320
|
+
* `Gone`, and closes the Gateway PDA in the *same* instruction
|
|
321
|
+
* (programs/ario-gar/src/instructions/gateway.rs::finalize_gone), so a
|
|
322
|
+
* gateway is never observably parked at `Gone`. Filtering on `Gone` matched
|
|
323
|
+
* nothing and left Leaving gateways un-GC'd. Time/delegation eligibility is
|
|
324
|
+
* still enforced on-chain (`finalize_gone` reverts early if the leave window
|
|
325
|
+
* hasn't elapsed or delegations remain), so over-returning not-yet-eligible
|
|
326
|
+
* Leaving gateways is safe — the tx just no-ops/reverts.
|
|
327
|
+
*
|
|
328
|
+
* @deprecated Prefer {@link getLeavingGateways} — same result, accurate name.
|
|
307
329
|
*/
|
|
308
330
|
getGoneGateways(): Promise<Array<{
|
|
309
331
|
pubkey: Address;
|
|
310
332
|
operator: Address;
|
|
311
333
|
}>>;
|
|
334
|
+
/**
|
|
335
|
+
* Enumerate Gateway PDAs whose `status == Leaving` — the persistent
|
|
336
|
+
* pre-finalization state that `finalizeGone` GC's. Alias for
|
|
337
|
+
* {@link getGoneGateways} with a name that matches the on-chain state.
|
|
338
|
+
*/
|
|
339
|
+
getLeavingGateways(): Promise<Array<{
|
|
340
|
+
pubkey: Address;
|
|
341
|
+
operator: Address;
|
|
342
|
+
}>>;
|
|
312
343
|
/**
|
|
313
344
|
* Enumerate Joined Gateway PDAs whose delegation has been DISABLED
|
|
314
345
|
* (`allow_delegated_staking == false`) yet still hold delegated stake
|
|
@@ -26,6 +26,29 @@ import type { mARIOToken } from '../types/token.js';
|
|
|
26
26
|
import { deserializeEpochSettingsFull } from './deserialize.js';
|
|
27
27
|
import { SolanaARIOReadable } from './io-readable.js';
|
|
28
28
|
import type { SolanaRpcSubscriptions, SolanaSigner, SolanaWriteConfig } from './types.js';
|
|
29
|
+
/**
|
|
30
|
+
* Pick the swapped-gateway operator that `finalize_gone` needs as a writable
|
|
31
|
+
* `remaining_accounts[0]`.
|
|
32
|
+
*
|
|
33
|
+
* `finalize_gone` reclaims a gateway's slot from the compact GatewayRegistry by
|
|
34
|
+
* moving the LAST active slot into it and rewriting that swapped gateway's
|
|
35
|
+
* stored `registry_index`. When the finalized gateway is NOT already the last
|
|
36
|
+
* slot, the on-chain handler requires the swapped Gateway PDA (writable) at
|
|
37
|
+
* `remaining_accounts[0]`; when it IS the last slot, no swap occurs and no
|
|
38
|
+
* extra account is needed. See
|
|
39
|
+
* `programs/ario-gar/src/instructions/gateway.rs::finalize_gone`.
|
|
40
|
+
*
|
|
41
|
+
* `registryAddresses` MUST be the active registry operator addresses in slot
|
|
42
|
+
* order (`getRegistryGatewayAddresses()` — length === on-chain
|
|
43
|
+
* `registry.count`), so `registryAddresses[length - 1]` is exactly the
|
|
44
|
+
* `registry.gateways[count - 1].address` the on-chain swap reads.
|
|
45
|
+
*
|
|
46
|
+
* @returns the swapped gateway's operator address, or `null` when the finalized
|
|
47
|
+
* gateway already occupies the last slot.
|
|
48
|
+
* @throws if `registryIndex` is outside the active registry count (mirrors the
|
|
49
|
+
* on-chain `index < registry.count` guard, surfacing a stale index early).
|
|
50
|
+
*/
|
|
51
|
+
export declare function selectFinalizeGoneSwapOperator(registryIndex: number, registryAddresses: string[]): string | null;
|
|
29
52
|
/**
|
|
30
53
|
* Split a primary name into its undername + base parts using the same rule
|
|
31
54
|
* as the on-chain `splitn(2, '_')` in `programs/ario-core/src/instructions/primary_name.rs`:
|
package/lib/types/types/io.d.ts
CHANGED
|
@@ -144,6 +144,16 @@ export type EligibleDistribution = {
|
|
|
144
144
|
gatewayAddress: WalletAddress;
|
|
145
145
|
cursorId: string;
|
|
146
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* The six ARIO supply buckets. They are mutually exclusive and sum to `total`:
|
|
149
|
+
* circulating + locked + staked + delegated + withdrawn + protocolBalance === total
|
|
150
|
+
*
|
|
151
|
+
* `protocolBalance` is the protocol **reward reserve** (the pool epoch rewards
|
|
152
|
+
* are paid from) — on Solana this is the live balance of the protocol token
|
|
153
|
+
* account, matching AO's `protocolBalance` (the qNvAoz0 reserve). It is NOT the
|
|
154
|
+
* on-chain `ArioConfig.protocol_balance` accounting field, which folds the
|
|
155
|
+
* staking buckets in and would double-count.
|
|
156
|
+
*/
|
|
147
157
|
export type TokenSupplyData = {
|
|
148
158
|
total: number;
|
|
149
159
|
circulating: number;
|
package/lib/types/version.d.ts
CHANGED