@ar.io/sdk 4.0.0-solana.37 → 4.0.0-solana.39
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.
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* registry's signer) acts as the rent payer for everything bundled here.
|
|
54
54
|
*/
|
|
55
55
|
import { address, } from '@solana/kit';
|
|
56
|
-
import { getAddAclPageInstruction, getCloseAclConfigInstruction, getCloseAclPageInstruction, getRecordAclControllerInstructionAsync,
|
|
56
|
+
import { getAddAclPageInstruction, getCloseAclConfigInstruction, getCloseAclPageInstruction, getRecordAclControllerInstructionAsync, getRecordAclOwnerInstructionAsync, getRegisterAclConfigInstruction, getRemoveAclControllerInstructionAsync, getRemoveAclOwnerInstructionAsync, } from '@ar.io/solana-contracts/ant';
|
|
57
57
|
import { SolanaANTRegistryReadable, } from './ant-registry-readable.js';
|
|
58
58
|
import { ACL_ROLE_CONTROLLER, ACL_ROLE_OWNER, MAX_ACL_PAGE_ENTRIES, } from './constants.js';
|
|
59
59
|
import { deserializeAclConfig, deserializeAclPage } from './deserialize.js';
|
|
@@ -204,7 +204,7 @@ export class SolanaANTRegistryWriteable extends SolanaANTRegistryReadable {
|
|
|
204
204
|
const [aclConfig] = await getAclConfigPDA(params.user, this.antProgram);
|
|
205
205
|
const [aclPage] = await getAclPagePDA(params.user, params.pageIdx, this.antProgram);
|
|
206
206
|
if (params.role === 'owner') {
|
|
207
|
-
return
|
|
207
|
+
return getRecordAclOwnerInstructionAsync({ asset: params.asset, aclConfig, aclPage, payer: this.signer }, { programAddress: this.antProgram });
|
|
208
208
|
}
|
|
209
209
|
return getRecordAclControllerInstructionAsync({ asset: params.asset, aclConfig, aclPage, payer: this.signer }, { programAddress: this.antProgram });
|
|
210
210
|
}
|
|
@@ -213,7 +213,7 @@ export class SolanaANTRegistryWriteable extends SolanaANTRegistryReadable {
|
|
|
213
213
|
const [aclConfig] = await getAclConfigPDA(params.user, this.antProgram);
|
|
214
214
|
const [aclPage] = await getAclPagePDA(params.user, params.pageIdx, this.antProgram);
|
|
215
215
|
if (params.role === 'owner') {
|
|
216
|
-
return
|
|
216
|
+
return getRemoveAclOwnerInstructionAsync({ asset: params.asset, aclConfig, aclPage, payer: this.signer }, { programAddress: this.antProgram });
|
|
217
217
|
}
|
|
218
218
|
return getRemoveAclControllerInstructionAsync({ asset: params.asset, aclConfig, aclPage, payer: this.signer }, { programAddress: this.antProgram });
|
|
219
219
|
}
|
|
@@ -1193,6 +1193,16 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1193
1193
|
arnsProgram: this.arnsProgram,
|
|
1194
1194
|
demandFactor: demandFactorAddr,
|
|
1195
1195
|
});
|
|
1196
|
+
// Roll the demand factor to the current period BEFORE pricing. The
|
|
1197
|
+
// `GetTokenCost` view reads the STORED demand factor, which is stale until a
|
|
1198
|
+
// crank (or a buy/extend) rolls it — but the `*FromFundingPlan` handlers
|
|
1199
|
+
// roll it inline before computing the cost. Without this, at a period
|
|
1200
|
+
// rollover the plan is sized to the old factor while the program charges
|
|
1201
|
+
// the new one → FundingPlanAmountMismatch (#6066). `update_demand_factor`
|
|
1202
|
+
// is permissionless + idempotent (no-op within the same period), and the
|
|
1203
|
+
// write is local to this simulation; the subsequent `GetTokenCost` in the
|
|
1204
|
+
// same tx sees the rolled value.
|
|
1205
|
+
const updateDfIx = getUpdateDemandFactorInstruction({ demandFactor: demandFactorAddr, payer: this.signer }, { programAddress: this.arnsProgram });
|
|
1196
1206
|
const ix = await getGetTokenCostInstructionAsync({
|
|
1197
1207
|
demandFactor: demandFactorAddr,
|
|
1198
1208
|
payer: this.signer,
|
|
@@ -1205,7 +1215,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1205
1215
|
const { value: latestBlockhash } = await this.rpc
|
|
1206
1216
|
.getLatestBlockhash()
|
|
1207
1217
|
.send();
|
|
1208
|
-
const message = pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayerSigner(this.signer, m), (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), (m) => appendTransactionMessageInstructions([ix], m));
|
|
1218
|
+
const message = pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayerSigner(this.signer, m), (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), (m) => appendTransactionMessageInstructions([updateDfIx, ix], m));
|
|
1209
1219
|
const compiled = compileTransaction(message);
|
|
1210
1220
|
const wire = getBase64EncodedWireTransaction(compiled);
|
|
1211
1221
|
const sim = await this.rpc
|
|
@@ -2661,6 +2671,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2661
2671
|
const enableCompound = opts.enableCompound ?? true;
|
|
2662
2672
|
const compoundMinPending = opts.compoundMinPendingRewards ?? 0;
|
|
2663
2673
|
const enableDemandFactorRoll = opts.enableDemandFactorRoll ?? true;
|
|
2674
|
+
const enablePrune = opts.enablePrune ?? true;
|
|
2664
2675
|
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
2665
2676
|
const settings = await this.getEpochSettingsFull();
|
|
2666
2677
|
if (!settings.enabled)
|
|
@@ -2714,9 +2725,19 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2714
2725
|
const { id } = await this.prescribeWithPrediction(targetEpochIndex, nameRegistryAccount);
|
|
2715
2726
|
return { action: 'prescribe', epochIndex: targetEpochIndex, txId: id };
|
|
2716
2727
|
}
|
|
2717
|
-
// Observations happen while the epoch is live.
|
|
2718
|
-
|
|
2728
|
+
// Observations happen while the epoch is live. This is the dominant idle
|
|
2729
|
+
// window — fold returned-name pruning in here so it isn't starved on
|
|
2730
|
+
// clusters whose epochs park here (e.g. imported gateways that can't
|
|
2731
|
+
// observe → distribution never advances → the post-distribution tail below
|
|
2732
|
+
// is never reached).
|
|
2733
|
+
if (now < epoch.endTimestamp) {
|
|
2734
|
+
if (enablePrune) {
|
|
2735
|
+
const pruned = await this.maybePruneReturnedNamesStep(opts, now);
|
|
2736
|
+
if (pruned)
|
|
2737
|
+
return pruned;
|
|
2738
|
+
}
|
|
2719
2739
|
return { action: 'idle', reason: 'waiting_for_observations' };
|
|
2740
|
+
}
|
|
2720
2741
|
// Distribute (batched).
|
|
2721
2742
|
if (epoch.rewardsDistributed === 0) {
|
|
2722
2743
|
const gatewayAccounts = epoch.activeGatewayCount > 0
|
|
@@ -2761,6 +2782,11 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2761
2782
|
if (rolled)
|
|
2762
2783
|
return rolled;
|
|
2763
2784
|
}
|
|
2785
|
+
if (enablePrune) {
|
|
2786
|
+
const pruned = await this.maybePruneReturnedNamesStep(opts, now);
|
|
2787
|
+
if (pruned)
|
|
2788
|
+
return pruned;
|
|
2789
|
+
}
|
|
2764
2790
|
// Current epoch fully processed — create the next once its start arrives.
|
|
2765
2791
|
if (now >= nextEpochStart) {
|
|
2766
2792
|
const { id } = await this.createEpoch();
|
|
@@ -2805,6 +2831,38 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2805
2831
|
const { id } = await this.updateDemandFactor();
|
|
2806
2832
|
return { action: 'update_demand_factor', txId: id };
|
|
2807
2833
|
}
|
|
2834
|
+
/** Wall-clock (ms) of the last returned-name prune scan; throttles the
|
|
2835
|
+
* getProgramAccounts scan below the crank poll rate. */
|
|
2836
|
+
lastReturnedNamePruneScanMs = 0;
|
|
2837
|
+
/**
|
|
2838
|
+
* One prune batch over ReturnedName PDAs whose 14-day auction window has
|
|
2839
|
+
* elapsed (≤ {@link CrankEpochStepOptions.pruneBatchSize} per tx), or `null`
|
|
2840
|
+
* when none are due / the scan is throttled. Scans the PDAs directly — it does
|
|
2841
|
+
* NOT gate on `config.next_returned_names_prune_timestamp`, which is never set
|
|
2842
|
+
* for imported returned names and would strand them. The contract re-checks
|
|
2843
|
+
* each account's window, so a slightly-skewed client clock is safe.
|
|
2844
|
+
*/
|
|
2845
|
+
async maybePruneReturnedNamesStep(opts, now) {
|
|
2846
|
+
const scanInterval = opts.pruneScanIntervalMs ?? 60_000;
|
|
2847
|
+
const wallNow = Date.now();
|
|
2848
|
+
if (wallNow - this.lastReturnedNamePruneScanMs < scanInterval)
|
|
2849
|
+
return null;
|
|
2850
|
+
this.lastReturnedNamePruneScanMs = wallNow;
|
|
2851
|
+
const expired = await this.getExpiredReturnedNames(now);
|
|
2852
|
+
if (expired.length === 0)
|
|
2853
|
+
return null;
|
|
2854
|
+
const batchSize = opts.pruneBatchSize ?? 15;
|
|
2855
|
+
const batch = expired.slice(0, batchSize).map((r) => r.pubkey);
|
|
2856
|
+
const { id } = await this.pruneReturnedNames({
|
|
2857
|
+
maxNames: batch.length,
|
|
2858
|
+
returnedNames: batch,
|
|
2859
|
+
});
|
|
2860
|
+
return {
|
|
2861
|
+
action: 'prune_returned_names',
|
|
2862
|
+
txId: id,
|
|
2863
|
+
progress: { index: batch.length, total: expired.length },
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2808
2866
|
/**
|
|
2809
2867
|
* The DemandFactor account's stored period + period-zero start (seconds) —
|
|
2810
2868
|
* the gate for {@link maybeRollDemandFactorStep}. `null` if the account
|
package/lib/esm/version.js
CHANGED
|
@@ -122,7 +122,7 @@ export declare function buildObservationBitmap(registryAddresses: string[], fail
|
|
|
122
122
|
*/
|
|
123
123
|
export declare function encodeReportTxId(reportTxId: string | undefined): Buffer;
|
|
124
124
|
/** The single on-chain action a {@link SolanaARIOWriteable.crankEpochStep} call performed. */
|
|
125
|
-
export type CrankAction = 'create' | 'tally' | 'prescribe' | 'distribute' | 'compound' | 'update_demand_factor' | 'close' | 'idle';
|
|
125
|
+
export type CrankAction = 'create' | 'tally' | 'prescribe' | 'distribute' | 'compound' | 'update_demand_factor' | 'prune_returned_names' | 'close' | 'idle';
|
|
126
126
|
/** Options for {@link SolanaARIOWriteable.crankEpochStep}. */
|
|
127
127
|
export interface CrankEpochStepOptions {
|
|
128
128
|
/** Gateways per tally/distribute batch. Default 30. */
|
|
@@ -158,6 +158,24 @@ export interface CrankEpochStepOptions {
|
|
|
158
158
|
* between buys) current. Default true. Runs only in the idle tail.
|
|
159
159
|
*/
|
|
160
160
|
enableDemandFactorRoll?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Prune expired ReturnedName PDAs (auction window elapsed) as part of the
|
|
163
|
+
* crank. Default true. Runs whenever the epoch lifecycle is otherwise idle
|
|
164
|
+
* (the live observation window AND the post-distribution tail), one batch per
|
|
165
|
+
* step. It scans the ReturnedName PDAs DIRECTLY (via getExpiredReturnedNames)
|
|
166
|
+
* and does NOT consult `config.next_returned_names_prune_timestamp` — that
|
|
167
|
+
* hint is only set for on-chain returns and is never updated for imported
|
|
168
|
+
* returned names, so trusting it strands imported auctions forever.
|
|
169
|
+
*/
|
|
170
|
+
enablePrune?: boolean;
|
|
171
|
+
/** ReturnedName PDAs to prune per tx (u8, max 255). Default 15. */
|
|
172
|
+
pruneBatchSize?: number;
|
|
173
|
+
/**
|
|
174
|
+
* Minimum wall-clock gap between returned-name prune SCANS (ms). The scan is a
|
|
175
|
+
* `getProgramAccounts` over ReturnedName PDAs, so this throttles it below the
|
|
176
|
+
* crank poll rate. Default 60_000 (1 min). Set 0 to scan every step (tests).
|
|
177
|
+
*/
|
|
178
|
+
pruneScanIntervalMs?: number;
|
|
161
179
|
/** Unix seconds; defaults to the wall clock. Injectable for testing. */
|
|
162
180
|
now?: number;
|
|
163
181
|
}
|
|
@@ -727,6 +745,18 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
727
745
|
* itself is idempotent.
|
|
728
746
|
*/
|
|
729
747
|
private maybeRollDemandFactorStep;
|
|
748
|
+
/** Wall-clock (ms) of the last returned-name prune scan; throttles the
|
|
749
|
+
* getProgramAccounts scan below the crank poll rate. */
|
|
750
|
+
private lastReturnedNamePruneScanMs;
|
|
751
|
+
/**
|
|
752
|
+
* One prune batch over ReturnedName PDAs whose 14-day auction window has
|
|
753
|
+
* elapsed (≤ {@link CrankEpochStepOptions.pruneBatchSize} per tx), or `null`
|
|
754
|
+
* when none are due / the scan is throttled. Scans the PDAs directly — it does
|
|
755
|
+
* NOT gate on `config.next_returned_names_prune_timestamp`, which is never set
|
|
756
|
+
* for imported returned names and would strand them. The contract re-checks
|
|
757
|
+
* each account's window, so a slightly-skewed client clock is safe.
|
|
758
|
+
*/
|
|
759
|
+
private maybePruneReturnedNamesStep;
|
|
730
760
|
/**
|
|
731
761
|
* The DemandFactor account's stored period + period-zero start (seconds) —
|
|
732
762
|
* the gate for {@link maybeRollDemandFactorStep}. `null` if the account
|
package/lib/types/version.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ar.io/sdk",
|
|
3
|
-
"version": "4.0.0-solana.
|
|
3
|
+
"version": "4.0.0-solana.39",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ar-io/ar-io-sdk.git"
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"typescript": "^5.1.6"
|
|
115
115
|
},
|
|
116
116
|
"dependencies": {
|
|
117
|
-
"@ar.io/solana-contracts": "0.
|
|
117
|
+
"@ar.io/solana-contracts": "0.8.0-staging.18",
|
|
118
118
|
"@noble/hashes": "^1.8.0",
|
|
119
119
|
"@solana-program/address-lookup-table": "^0.11.0",
|
|
120
120
|
"@solana-program/compute-budget": "^0.15.0",
|