@ar.io/sdk 3.24.0 → 4.0.0-alpha.2
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 +757 -589
- package/lib/esm/cli/cli.js +188 -152
- package/lib/esm/cli/commands/antCommands.js +23 -58
- package/lib/esm/cli/commands/arnsPurchaseCommands.js +48 -30
- package/lib/esm/cli/commands/escrowCommands.js +227 -0
- package/lib/esm/cli/commands/gatewayWriteCommands.js +140 -23
- package/lib/esm/cli/commands/pruneCommands.js +154 -0
- package/lib/esm/cli/commands/readCommands.js +22 -3
- package/lib/esm/cli/commands/transfer.js +6 -6
- package/lib/esm/cli/options.js +124 -58
- package/lib/esm/cli/utils.js +303 -175
- package/lib/esm/common/ant-registry.js +17 -143
- package/lib/esm/common/ant.js +44 -1167
- package/lib/esm/common/faucet.js +17 -6
- package/lib/esm/common/index.js +0 -4
- package/lib/esm/common/io.js +25 -1412
- package/lib/esm/constants.js +13 -19
- package/lib/esm/solana/ant-readable.js +724 -0
- package/lib/esm/solana/ant-registry-readable.js +133 -0
- package/lib/esm/solana/ant-registry-writeable.js +472 -0
- package/lib/esm/solana/ant-writeable.js +384 -0
- package/lib/esm/solana/ata.js +70 -0
- package/lib/esm/solana/canonical-message.js +128 -0
- package/lib/esm/solana/clusters.js +111 -0
- package/lib/esm/solana/constants.js +146 -0
- package/lib/esm/solana/delegation-math.js +112 -0
- package/lib/esm/solana/deserialize.js +711 -0
- package/lib/esm/solana/escrow.js +839 -0
- package/lib/{cjs/utils/json.js → esm/solana/events.js} +15 -10
- package/lib/esm/solana/funding-plan.js +699 -0
- package/lib/esm/solana/index.js +126 -0
- package/lib/esm/solana/instruction.js +39 -0
- package/lib/esm/solana/io-readable.js +2182 -0
- package/lib/esm/solana/io-writeable.js +3196 -0
- package/lib/esm/solana/json-rpc.js +90 -0
- package/lib/esm/solana/metadata.js +81 -0
- package/lib/esm/solana/mpl-core.js +192 -0
- package/lib/esm/solana/pda.js +332 -0
- package/lib/esm/solana/predict-prescribed-observers.js +110 -0
- package/lib/esm/solana/retry.js +117 -0
- package/lib/esm/solana/rpc-circuit-breaker.js +258 -0
- package/lib/esm/solana/send.js +372 -0
- package/lib/esm/solana/spawn-ant.js +224 -0
- package/lib/esm/solana/types.js +1 -0
- package/lib/esm/types/ant.js +27 -15
- package/lib/esm/types/io.js +8 -11
- package/lib/esm/utils/ant.js +0 -63
- package/lib/esm/utils/index.js +0 -3
- package/lib/esm/version.js +1 -1
- package/lib/types/cli/commands/antCommands.d.ts +5 -13
- package/lib/types/cli/commands/arnsPurchaseCommands.d.ts +33 -7
- package/lib/types/cli/commands/escrowCommands.d.ts +68 -0
- package/lib/types/cli/commands/gatewayWriteCommands.d.ts +12 -11
- package/lib/types/cli/commands/pruneCommands.d.ts +31 -0
- package/lib/types/cli/commands/readCommands.d.ts +27 -22
- package/lib/types/cli/commands/transfer.d.ts +9 -9
- package/lib/types/cli/options.d.ts +76 -21
- package/lib/types/cli/types.d.ts +11 -13
- package/lib/types/cli/utils.d.ts +71 -31
- package/lib/types/common/ant-registry.d.ts +49 -47
- package/lib/types/common/ant.d.ts +54 -539
- package/lib/types/common/faucet.d.ts +20 -8
- package/lib/types/common/index.d.ts +0 -3
- package/lib/types/common/io.d.ts +66 -258
- package/lib/types/constants.d.ts +11 -18
- package/lib/types/solana/ant-readable.d.ts +180 -0
- package/lib/types/solana/ant-registry-readable.d.ts +105 -0
- package/lib/types/solana/ant-registry-writeable.d.ts +249 -0
- package/lib/types/solana/ant-writeable.d.ts +177 -0
- package/lib/types/solana/ata.d.ts +44 -0
- package/lib/types/solana/canonical-message.d.ts +121 -0
- package/lib/types/solana/clusters.d.ts +109 -0
- package/lib/types/solana/constants.d.ts +119 -0
- package/lib/types/solana/delegation-math.d.ts +45 -0
- package/lib/types/solana/deserialize.d.ts +262 -0
- package/lib/types/solana/escrow.d.ts +480 -0
- package/lib/types/solana/events.d.ts +38 -0
- package/lib/types/solana/funding-plan.d.ts +225 -0
- package/lib/types/solana/index.d.ts +87 -0
- package/lib/types/solana/instruction.d.ts +39 -0
- package/lib/types/solana/io-readable.d.ts +499 -0
- package/lib/types/solana/io-writeable.d.ts +893 -0
- package/lib/types/solana/json-rpc.d.ts +47 -0
- package/lib/types/solana/metadata.d.ts +84 -0
- package/lib/types/solana/mpl-core.d.ts +120 -0
- package/lib/types/solana/pda.d.ts +95 -0
- package/lib/types/solana/predict-prescribed-observers.d.ts +28 -0
- package/lib/types/solana/retry.d.ts +62 -0
- package/lib/types/solana/rpc-circuit-breaker.d.ts +78 -0
- package/lib/types/solana/send.d.ts +94 -0
- package/lib/types/solana/spawn-ant.d.ts +145 -0
- package/lib/types/solana/types.d.ts +82 -0
- package/lib/types/types/ant-registry.d.ts +43 -4
- package/lib/types/types/ant.d.ts +114 -96
- package/lib/types/types/common.d.ts +18 -74
- package/lib/types/types/faucet.d.ts +2 -2
- package/lib/types/types/io.d.ts +244 -158
- package/lib/types/types/token.d.ts +0 -12
- package/lib/types/utils/ant.d.ts +1 -12
- package/lib/types/utils/index.d.ts +0 -3
- package/lib/types/version.d.ts +1 -1
- package/package.json +36 -33
- package/lib/cjs/cli/cli.js +0 -822
- package/lib/cjs/cli/commands/antCommands.js +0 -113
- package/lib/cjs/cli/commands/arnsPurchaseCommands.js +0 -212
- package/lib/cjs/cli/commands/gatewayWriteCommands.js +0 -210
- package/lib/cjs/cli/commands/readCommands.js +0 -215
- package/lib/cjs/cli/commands/transfer.js +0 -159
- package/lib/cjs/cli/options.js +0 -470
- package/lib/cjs/cli/types.js +0 -2
- package/lib/cjs/cli/utils.js +0 -639
- package/lib/cjs/common/ant-registry.js +0 -155
- package/lib/cjs/common/ant-versions.js +0 -93
- package/lib/cjs/common/ant.js +0 -1182
- package/lib/cjs/common/arweave.js +0 -27
- package/lib/cjs/common/contracts/ao-process.js +0 -224
- package/lib/cjs/common/error.js +0 -64
- package/lib/cjs/common/faucet.js +0 -150
- package/lib/cjs/common/hyperbeam/hb.js +0 -173
- package/lib/cjs/common/index.js +0 -42
- package/lib/cjs/common/io.js +0 -1423
- package/lib/cjs/common/logger.js +0 -83
- package/lib/cjs/common/loggers/winston.js +0 -68
- package/lib/cjs/common/marketplace.js +0 -731
- package/lib/cjs/common/turbo.js +0 -223
- package/lib/cjs/constants.js +0 -41
- package/lib/cjs/node/index.js +0 -39
- package/lib/cjs/package.json +0 -1
- package/lib/cjs/types/ant-registry.js +0 -2
- package/lib/cjs/types/ant.js +0 -168
- package/lib/cjs/types/common.js +0 -2
- package/lib/cjs/types/faucet.js +0 -2
- package/lib/cjs/types/index.js +0 -37
- package/lib/cjs/types/io.js +0 -51
- package/lib/cjs/types/token.js +0 -116
- package/lib/cjs/utils/ant.js +0 -108
- package/lib/cjs/utils/ao.js +0 -432
- package/lib/cjs/utils/arweave.js +0 -285
- package/lib/cjs/utils/base64.js +0 -62
- package/lib/cjs/utils/hash.js +0 -56
- package/lib/cjs/utils/index.js +0 -38
- package/lib/cjs/utils/processes.js +0 -173
- package/lib/cjs/utils/random.js +0 -30
- package/lib/cjs/utils/schema.js +0 -15
- package/lib/cjs/utils/url.js +0 -37
- package/lib/cjs/version.js +0 -20
- package/lib/cjs/web/index.js +0 -41
- package/lib/esm/common/ant-versions.js +0 -87
- package/lib/esm/common/arweave.js +0 -21
- package/lib/esm/common/contracts/ao-process.js +0 -220
- package/lib/esm/common/hyperbeam/hb.js +0 -169
- package/lib/esm/common/marketplace.js +0 -724
- package/lib/esm/common/turbo.js +0 -215
- package/lib/esm/node/index.js +0 -20
- package/lib/esm/utils/ao.js +0 -420
- package/lib/esm/utils/arweave.js +0 -271
- package/lib/esm/utils/processes.js +0 -167
- package/lib/esm/web/index.js +0 -20
- package/lib/types/common/ant-versions.d.ts +0 -39
- package/lib/types/common/arweave.d.ts +0 -17
- package/lib/types/common/contracts/ao-process.d.ts +0 -47
- package/lib/types/common/hyperbeam/hb.d.ts +0 -88
- package/lib/types/common/marketplace.d.ts +0 -568
- package/lib/types/common/turbo.d.ts +0 -61
- package/lib/types/node/index.d.ts +0 -20
- package/lib/types/utils/ao.d.ts +0 -80
- package/lib/types/utils/arweave.d.ts +0 -79
- package/lib/types/utils/processes.d.ts +0 -39
- package/lib/types/web/index.d.ts +0 -20
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Solana implementation of ANT (Arweave Name Token) read interface.
|
|
18
|
+
*
|
|
19
|
+
* Reads ANT state from Metaplex Core NFT + PDA accounts on Solana.
|
|
20
|
+
* Each ANT is a Metaplex Core NFT with extended state in PDAs:
|
|
21
|
+
* - AntConfig: name, ticker, logo, description, keywords, owner
|
|
22
|
+
* - AntControllers: list of controller pubkeys
|
|
23
|
+
* - AntRecord: undername records (transactionId, ttl, priority, etc.)
|
|
24
|
+
*/
|
|
25
|
+
import { address, fetchEncodedAccount, fetchEncodedAccounts, } from '@solana/kit';
|
|
26
|
+
import bs58 from 'bs58';
|
|
27
|
+
import { createHash as __createHash } from 'crypto';
|
|
28
|
+
import { ANT_RECORD_DISCRIMINATOR, ANT_RECORD_METADATA_DISCRIMINATOR, decodeAntConfig, decodeAntControllers, getAntRecordDecoder, getAntRecordMetadataDecoder, } from '@ar.io/solana-contracts/ant';
|
|
29
|
+
import { Logger } from '../common/logger.js';
|
|
30
|
+
import { SolanaANTRegistryReadable } from './ant-registry-readable.js';
|
|
31
|
+
import { ANT_CONFIG_VERSION, ARIO_ANT_PROGRAM_ID } from './constants.js';
|
|
32
|
+
import { getAntConfigPDA, getAntControllersPDA, getAntRecordMetadataPDA, getAntRecordPDA, } from './pda.js';
|
|
33
|
+
import { withRetry } from './retry.js';
|
|
34
|
+
/**
|
|
35
|
+
* Solana-backed read-only client for a single ANT (Arweave Name Token).
|
|
36
|
+
*
|
|
37
|
+
* Usage:
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { createSolanaRpc } from '@solana/kit';
|
|
40
|
+
* import { SolanaANTReadable } from '@ar.io/sdk/solana';
|
|
41
|
+
*
|
|
42
|
+
* const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
|
|
43
|
+
* const ant = new SolanaANTReadable({
|
|
44
|
+
* rpc,
|
|
45
|
+
* processId: 'MetaplexCoreAssetAddress...',
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* const owner = await ant.getOwner();
|
|
49
|
+
* const record = await ant.getRecord({ undername: '@' });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class SolanaANTReadable {
|
|
53
|
+
processId;
|
|
54
|
+
rpc;
|
|
55
|
+
commitment;
|
|
56
|
+
logger;
|
|
57
|
+
antProgram;
|
|
58
|
+
mint;
|
|
59
|
+
/**
|
|
60
|
+
* Composed registry instance — the source of truth for the per-user
|
|
61
|
+
* paginated ACL (ADR-012). Sharing one instance across the read and
|
|
62
|
+
* write classes keeps program id / commitment / RPC configuration
|
|
63
|
+
* coherent for both `accessControlList` reads and the maintenance
|
|
64
|
+
* planner used during writes.
|
|
65
|
+
*/
|
|
66
|
+
registry;
|
|
67
|
+
constructor(config) {
|
|
68
|
+
this.processId = config.processId;
|
|
69
|
+
this.rpc = config.rpc;
|
|
70
|
+
this.commitment = config.commitment ?? 'confirmed';
|
|
71
|
+
this.logger = config.logger ?? Logger.default;
|
|
72
|
+
this.antProgram = config.antProgramId ?? ARIO_ANT_PROGRAM_ID;
|
|
73
|
+
this.mint = address(config.processId);
|
|
74
|
+
this.registry =
|
|
75
|
+
config.registry ??
|
|
76
|
+
new SolanaANTRegistryReadable({
|
|
77
|
+
rpc: this.rpc,
|
|
78
|
+
commitment: this.commitment,
|
|
79
|
+
logger: this.logger,
|
|
80
|
+
antProgramId: this.antProgram,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build a `SolanaANTReadable` whose program id is read from the
|
|
85
|
+
* asset's `ANT Program` Attributes-plugin entry (ADR-016 / BD-100).
|
|
86
|
+
*
|
|
87
|
+
* Falls back to the canonical `ARIO_ANT_PROGRAM_ID` when the asset
|
|
88
|
+
* has no plugin section, no `ANT Program` trait, or any layer of the
|
|
89
|
+
* walk fails to decode — matching the on-chain leniency in
|
|
90
|
+
* `programs/ario-core/src/mpl_core.rs::read_ant_program`. This is the
|
|
91
|
+
* factory resolution paths should reach for: it does the asset fetch
|
|
92
|
+
* once, hands the resulting program id to the constructor, and
|
|
93
|
+
* shares one `SolanaANTRegistryReadable` instance with the new ANT.
|
|
94
|
+
*
|
|
95
|
+
* Use the plain constructor when the program id is already known
|
|
96
|
+
* (e.g. inside a freshly-spawned ANT flow where you've just minted
|
|
97
|
+
* the asset and know the program you targeted).
|
|
98
|
+
*/
|
|
99
|
+
static async fromAsset(config) {
|
|
100
|
+
const { fetchAntProgramFromAsset } = await import('./mpl-core.js');
|
|
101
|
+
const mint = address(config.processId);
|
|
102
|
+
const fromAsset = await fetchAntProgramFromAsset(config.rpc, mint, {
|
|
103
|
+
commitment: config.commitment ?? 'confirmed',
|
|
104
|
+
});
|
|
105
|
+
return new SolanaANTReadable({
|
|
106
|
+
rpc: config.rpc,
|
|
107
|
+
processId: config.processId,
|
|
108
|
+
commitment: config.commitment,
|
|
109
|
+
logger: config.logger,
|
|
110
|
+
antProgramId: fromAsset ?? ARIO_ANT_PROGRAM_ID,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async getAccount(pda) {
|
|
114
|
+
return withRetry(() => fetchEncodedAccount(this.rpc, pda, {
|
|
115
|
+
commitment: this.commitment,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
// =========================================
|
|
119
|
+
// Config reads
|
|
120
|
+
// =========================================
|
|
121
|
+
async fetchConfig() {
|
|
122
|
+
const [pda] = await getAntConfigPDA(this.mint, this.antProgram);
|
|
123
|
+
const account = await this.getAccount(pda);
|
|
124
|
+
if (!account.exists) {
|
|
125
|
+
throw new Error(`ANT config not found for ${this.processId}`);
|
|
126
|
+
}
|
|
127
|
+
const decoded = decodeAntConfig(account).data;
|
|
128
|
+
return {
|
|
129
|
+
mint: decoded.mint,
|
|
130
|
+
name: decoded.name,
|
|
131
|
+
ticker: decoded.ticker,
|
|
132
|
+
logo: decoded.logo,
|
|
133
|
+
description: decoded.description,
|
|
134
|
+
keywords: decoded.keywords,
|
|
135
|
+
owner: decoded.lastKnownOwner,
|
|
136
|
+
version: decoded.version.major,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async fetchControllers() {
|
|
140
|
+
const [pda] = await getAntControllersPDA(this.mint, this.antProgram);
|
|
141
|
+
const account = await this.getAccount(pda);
|
|
142
|
+
if (!account.exists) {
|
|
143
|
+
return { mint: this.processId, controllers: [] };
|
|
144
|
+
}
|
|
145
|
+
const decoded = decodeAntControllers(account).data;
|
|
146
|
+
return {
|
|
147
|
+
mint: decoded.mint,
|
|
148
|
+
controllers: decoded.controllers.map((c) => c),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Fetch AntConfig + AntControllers in a single `getMultipleAccounts` round
|
|
153
|
+
* trip (instead of two single-account reads). Used by `getState` to shave one
|
|
154
|
+
* RPC per ANT — meaningful when a UI loads many ANTs.
|
|
155
|
+
*/
|
|
156
|
+
async _fetchConfigAndControllers() {
|
|
157
|
+
const [[configPda], [controllersPda]] = await Promise.all([
|
|
158
|
+
getAntConfigPDA(this.mint, this.antProgram),
|
|
159
|
+
getAntControllersPDA(this.mint, this.antProgram),
|
|
160
|
+
]);
|
|
161
|
+
const [configAccount, controllersAccount] = await withRetry(() => fetchEncodedAccounts(this.rpc, [configPda, controllersPda], {
|
|
162
|
+
commitment: this.commitment,
|
|
163
|
+
}));
|
|
164
|
+
if (!configAccount.exists) {
|
|
165
|
+
throw new Error(`ANT config not found for ${this.processId}`);
|
|
166
|
+
}
|
|
167
|
+
const decodedConfig = decodeAntConfig(configAccount).data;
|
|
168
|
+
const config = {
|
|
169
|
+
mint: decodedConfig.mint,
|
|
170
|
+
name: decodedConfig.name,
|
|
171
|
+
ticker: decodedConfig.ticker,
|
|
172
|
+
logo: decodedConfig.logo,
|
|
173
|
+
description: decodedConfig.description,
|
|
174
|
+
keywords: decodedConfig.keywords,
|
|
175
|
+
owner: decodedConfig.lastKnownOwner,
|
|
176
|
+
version: decodedConfig.version.major,
|
|
177
|
+
};
|
|
178
|
+
const controllers = controllersAccount.exists
|
|
179
|
+
? decodeAntControllers(controllersAccount).data.controllers.map((c) => c)
|
|
180
|
+
: [];
|
|
181
|
+
return { config, controllers };
|
|
182
|
+
}
|
|
183
|
+
async getOwner(_opts) {
|
|
184
|
+
const config = await this.fetchConfig();
|
|
185
|
+
return config.owner;
|
|
186
|
+
}
|
|
187
|
+
/** Get the on-chain schema version of this ANT's config. */
|
|
188
|
+
async getConfigVersion() {
|
|
189
|
+
const config = await this.fetchConfig();
|
|
190
|
+
return config.version;
|
|
191
|
+
}
|
|
192
|
+
/** Check if this ANT needs a schema migration to the latest version. */
|
|
193
|
+
async needsMigration() {
|
|
194
|
+
const version = await this.getConfigVersion();
|
|
195
|
+
return version < ANT_CONFIG_VERSION;
|
|
196
|
+
}
|
|
197
|
+
async getName(_opts) {
|
|
198
|
+
const config = await this.fetchConfig();
|
|
199
|
+
return config.name;
|
|
200
|
+
}
|
|
201
|
+
async getTicker(_opts) {
|
|
202
|
+
const config = await this.fetchConfig();
|
|
203
|
+
return config.ticker;
|
|
204
|
+
}
|
|
205
|
+
async getLogo(_opts) {
|
|
206
|
+
const config = await this.fetchConfig();
|
|
207
|
+
return config.logo;
|
|
208
|
+
}
|
|
209
|
+
async getControllers() {
|
|
210
|
+
const data = await this.fetchControllers();
|
|
211
|
+
return data.controllers;
|
|
212
|
+
}
|
|
213
|
+
// =========================================
|
|
214
|
+
// Record reads
|
|
215
|
+
// =========================================
|
|
216
|
+
async getRecord({ undername }, _opts) {
|
|
217
|
+
const [[recordPda], [metaPda]] = await Promise.all([
|
|
218
|
+
getAntRecordPDA(this.mint, undername, this.antProgram),
|
|
219
|
+
getAntRecordMetadataPDA(this.mint, undername, this.antProgram),
|
|
220
|
+
]);
|
|
221
|
+
const [recordAccount, metaAccount] = await withRetry(() => fetchEncodedAccounts(this.rpc, [recordPda, metaPda], {
|
|
222
|
+
commitment: this.commitment,
|
|
223
|
+
}));
|
|
224
|
+
if (!recordAccount.exists)
|
|
225
|
+
return undefined;
|
|
226
|
+
const recordDecoder = getAntRecordDecoder();
|
|
227
|
+
const metaDecoder = getAntRecordMetadataDecoder();
|
|
228
|
+
const record = recordDecoder.decode(new Uint8Array(recordAccount.data));
|
|
229
|
+
const meta = metaAccount.exists
|
|
230
|
+
? metaDecoder.decode(new Uint8Array(metaAccount.data))
|
|
231
|
+
: undefined;
|
|
232
|
+
return {
|
|
233
|
+
transactionId: record.target,
|
|
234
|
+
targetProtocol: record.targetProtocol,
|
|
235
|
+
ttlSeconds: record.ttlSeconds,
|
|
236
|
+
priority: record.priority?.__option === 'Some'
|
|
237
|
+
? record.priority.value
|
|
238
|
+
: undefined,
|
|
239
|
+
owner: record.owner?.__option === 'Some'
|
|
240
|
+
? record.owner.value
|
|
241
|
+
: undefined,
|
|
242
|
+
displayName: meta?.displayName?.__option === 'Some'
|
|
243
|
+
? meta.displayName.value
|
|
244
|
+
: undefined,
|
|
245
|
+
logo: meta?.recordLogo?.__option === 'Some'
|
|
246
|
+
? meta.recordLogo.value
|
|
247
|
+
: undefined,
|
|
248
|
+
description: meta?.recordDescription?.__option === 'Some'
|
|
249
|
+
? meta.recordDescription.value
|
|
250
|
+
: undefined,
|
|
251
|
+
keywords: meta?.recordKeywords?.__option === 'Some'
|
|
252
|
+
? meta.recordKeywords.value
|
|
253
|
+
: undefined,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async getRecords(opts) {
|
|
257
|
+
// Fetch all AntRecord accounts for this mint. AntRecordMetadata
|
|
258
|
+
// (displayName/logo/description/keywords) is a SECOND program scan and is
|
|
259
|
+
// only needed in detail/edit views, so skip it unless `includeMetadata` is
|
|
260
|
+
// set — halving the per-ANT request cost on list reads. See AntReadOptions.
|
|
261
|
+
const includeMetadata = opts?.includeMetadata === true;
|
|
262
|
+
const gpaFilter = (discriminator) => [
|
|
263
|
+
{
|
|
264
|
+
memcmp: {
|
|
265
|
+
offset: 0n,
|
|
266
|
+
bytes: discriminator,
|
|
267
|
+
encoding: 'base58',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
memcmp: {
|
|
272
|
+
offset: 8n,
|
|
273
|
+
bytes: this.mint,
|
|
274
|
+
encoding: 'base58',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
const [recordAccounts, metaAccounts] = (await Promise.all([
|
|
279
|
+
withRetry(() => this.rpc
|
|
280
|
+
.getProgramAccounts(this.antProgram, {
|
|
281
|
+
commitment: this.commitment,
|
|
282
|
+
encoding: 'base64',
|
|
283
|
+
filters: gpaFilter(bs58.encode(ANT_RECORD_DISCRIMINATOR)),
|
|
284
|
+
})
|
|
285
|
+
.send()),
|
|
286
|
+
includeMetadata
|
|
287
|
+
? withRetry(() => this.rpc
|
|
288
|
+
.getProgramAccounts(this.antProgram, {
|
|
289
|
+
commitment: this.commitment,
|
|
290
|
+
encoding: 'base64',
|
|
291
|
+
filters: gpaFilter(bs58.encode(ANT_RECORD_METADATA_DISCRIMINATOR)),
|
|
292
|
+
})
|
|
293
|
+
.send())
|
|
294
|
+
: Promise.resolve([]),
|
|
295
|
+
]));
|
|
296
|
+
const recordDecoder = getAntRecordDecoder();
|
|
297
|
+
const metaDecoder = getAntRecordMetadataDecoder();
|
|
298
|
+
// Index metadata by undername hash for O(1) lookup.
|
|
299
|
+
// AntRecordMetadata has undername_hash at offset 40 (8 disc + 32 mint).
|
|
300
|
+
const metaByHash = new Map();
|
|
301
|
+
for (const { account } of metaAccounts) {
|
|
302
|
+
try {
|
|
303
|
+
const buf = Buffer.from(account.data[0], 'base64');
|
|
304
|
+
const hash = buf.subarray(40, 72).toString('hex');
|
|
305
|
+
metaByHash.set(hash, metaDecoder.decode(new Uint8Array(buf)));
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Skip malformed
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const result = {};
|
|
312
|
+
let index = 0;
|
|
313
|
+
for (const { account } of recordAccounts) {
|
|
314
|
+
try {
|
|
315
|
+
const buf = Buffer.from(account.data[0], 'base64');
|
|
316
|
+
const record = recordDecoder.decode(new Uint8Array(buf));
|
|
317
|
+
const hash = __createHash('sha256')
|
|
318
|
+
.update(record.undername.toLowerCase())
|
|
319
|
+
.digest('hex');
|
|
320
|
+
const meta = metaByHash.get(hash);
|
|
321
|
+
result[record.undername] = {
|
|
322
|
+
transactionId: record.target,
|
|
323
|
+
targetProtocol: record.targetProtocol,
|
|
324
|
+
ttlSeconds: record.ttlSeconds,
|
|
325
|
+
priority: record.priority?.__option === 'Some'
|
|
326
|
+
? record.priority.value
|
|
327
|
+
: undefined,
|
|
328
|
+
owner: record.owner?.__option === 'Some'
|
|
329
|
+
? record.owner.value
|
|
330
|
+
: undefined,
|
|
331
|
+
displayName: meta?.displayName?.__option === 'Some'
|
|
332
|
+
? meta.displayName.value
|
|
333
|
+
: undefined,
|
|
334
|
+
logo: meta?.recordLogo?.__option === 'Some'
|
|
335
|
+
? meta.recordLogo.value
|
|
336
|
+
: undefined,
|
|
337
|
+
description: meta?.recordDescription?.__option === 'Some'
|
|
338
|
+
? meta.recordDescription.value
|
|
339
|
+
: undefined,
|
|
340
|
+
keywords: meta?.recordKeywords?.__option === 'Some'
|
|
341
|
+
? meta.recordKeywords.value
|
|
342
|
+
: undefined,
|
|
343
|
+
index: index++,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Skip malformed
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Bulk-load lightweight {@link ANTSummary} state for many ANTs in a handful
|
|
354
|
+
* of `getMultipleAccounts` calls instead of `N × getState`. For each mint it
|
|
355
|
+
* batches AntConfig + AntControllers + the apex (`@`) AntRecord — everything a
|
|
356
|
+
* portfolio/names table needs. Full undername records are NOT loaded here;
|
|
357
|
+
* fetch them lazily per-ANT via {@link getRecords}/{@link getState} when a
|
|
358
|
+
* name is opened.
|
|
359
|
+
*
|
|
360
|
+
* Requests: ~`ceil(3N / 100)` calls for N mints (10 → 1, 250 → 8), versus
|
|
361
|
+
* ~`4N` with per-ANT `getState`. Assumes every mint is deployed under this
|
|
362
|
+
* instance's `antProgram` (true for the standard AR.IO ANT program).
|
|
363
|
+
*
|
|
364
|
+
* Mints whose AntConfig doesn't exist are omitted from the result.
|
|
365
|
+
*/
|
|
366
|
+
async getANTSummaries(mints) {
|
|
367
|
+
const unique = Array.from(new Set(mints));
|
|
368
|
+
if (unique.length === 0)
|
|
369
|
+
return {};
|
|
370
|
+
// Derive config + controllers + apex('@') record PDAs for every mint.
|
|
371
|
+
const triples = await Promise.all(unique.map(async (m) => {
|
|
372
|
+
const mintAddr = address(m);
|
|
373
|
+
const [[configPda], [controllersPda], [apexPda]] = await Promise.all([
|
|
374
|
+
getAntConfigPDA(mintAddr, this.antProgram),
|
|
375
|
+
getAntControllersPDA(mintAddr, this.antProgram),
|
|
376
|
+
getAntRecordPDA(mintAddr, '@', this.antProgram),
|
|
377
|
+
]);
|
|
378
|
+
return { mint: m, configPda, controllersPda, apexPda };
|
|
379
|
+
}));
|
|
380
|
+
// Batch-fetch all PDAs (3 per mint) — getMultipleAccounts caps at 100.
|
|
381
|
+
const allPdas = triples.flatMap((t) => [
|
|
382
|
+
t.configPda,
|
|
383
|
+
t.controllersPda,
|
|
384
|
+
t.apexPda,
|
|
385
|
+
]);
|
|
386
|
+
const accounts = [];
|
|
387
|
+
for (let i = 0; i < allPdas.length; i += 100) {
|
|
388
|
+
const chunk = allPdas.slice(i, i + 100);
|
|
389
|
+
const res = await withRetry(() => fetchEncodedAccounts(this.rpc, chunk, { commitment: this.commitment }));
|
|
390
|
+
accounts.push(...res);
|
|
391
|
+
}
|
|
392
|
+
const recordDecoder = getAntRecordDecoder();
|
|
393
|
+
const result = {};
|
|
394
|
+
for (let i = 0; i < triples.length; i++) {
|
|
395
|
+
const { mint } = triples[i];
|
|
396
|
+
const configAccount = accounts[i * 3];
|
|
397
|
+
const controllersAccount = accounts[i * 3 + 1];
|
|
398
|
+
const apexAccount = accounts[i * 3 + 2];
|
|
399
|
+
if (!configAccount?.exists)
|
|
400
|
+
continue;
|
|
401
|
+
const config = decodeAntConfig(configAccount).data;
|
|
402
|
+
const controllers = controllersAccount?.exists
|
|
403
|
+
? decodeAntControllers(controllersAccount).data.controllers.map((c) => c)
|
|
404
|
+
: [];
|
|
405
|
+
let apexRecord;
|
|
406
|
+
if (apexAccount?.exists) {
|
|
407
|
+
const rec = recordDecoder.decode(new Uint8Array(apexAccount.data));
|
|
408
|
+
apexRecord = {
|
|
409
|
+
transactionId: rec.target,
|
|
410
|
+
targetProtocol: rec.targetProtocol,
|
|
411
|
+
ttlSeconds: rec.ttlSeconds,
|
|
412
|
+
priority: rec.priority?.__option === 'Some' ? rec.priority.value : undefined,
|
|
413
|
+
owner: rec.owner?.__option === 'Some'
|
|
414
|
+
? rec.owner.value
|
|
415
|
+
: undefined,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
result[mint] = {
|
|
419
|
+
processId: mint,
|
|
420
|
+
name: config.name,
|
|
421
|
+
ticker: config.ticker,
|
|
422
|
+
logo: config.logo,
|
|
423
|
+
description: config.description,
|
|
424
|
+
keywords: config.keywords,
|
|
425
|
+
owner: config.lastKnownOwner,
|
|
426
|
+
controllers,
|
|
427
|
+
apexRecord,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Bulk-load FULL {@link ANTState} (including all undername records) for many
|
|
434
|
+
* ANTs in a handful of calls instead of `N × getState`:
|
|
435
|
+
* - AntConfig + AntControllers for every mint via `getMultipleAccounts`
|
|
436
|
+
* (chunked at 100), and
|
|
437
|
+
* - ALL undername records via a SINGLE program-wide `getProgramAccounts`
|
|
438
|
+
* scan grouped by mint (offset 8), instead of one mint-filtered scan per
|
|
439
|
+
* ANT.
|
|
440
|
+
*
|
|
441
|
+
* Requests: ~`ceil(2N / 100) + 1` (+1 when `includeMetadata`) regardless of
|
|
442
|
+
* N — e.g. 10 ANTs → 2 calls, 250 → ~6 — versus ~`2N` with per-ANT
|
|
443
|
+
* `getState`. The records scan reads every ANT's records program-wide (cheap
|
|
444
|
+
* per account, one round trip); prefer per-ANT {@link getState} when you only
|
|
445
|
+
* need one ANT. Mints with no AntConfig are omitted.
|
|
446
|
+
*/
|
|
447
|
+
async getANTStates(mints, opts) {
|
|
448
|
+
const unique = Array.from(new Set(mints));
|
|
449
|
+
if (unique.length === 0)
|
|
450
|
+
return {};
|
|
451
|
+
// Config + controllers PDAs for every mint, batched (100 accounts/call).
|
|
452
|
+
const pairs = await Promise.all(unique.map(async (m) => {
|
|
453
|
+
const mintAddr = address(m);
|
|
454
|
+
const [[configPda], [controllersPda]] = await Promise.all([
|
|
455
|
+
getAntConfigPDA(mintAddr, this.antProgram),
|
|
456
|
+
getAntControllersPDA(mintAddr, this.antProgram),
|
|
457
|
+
]);
|
|
458
|
+
return { mint: m, configPda, controllersPda };
|
|
459
|
+
}));
|
|
460
|
+
const allPdas = pairs.flatMap((p) => [p.configPda, p.controllersPda]);
|
|
461
|
+
const accounts = [];
|
|
462
|
+
for (let i = 0; i < allPdas.length; i += 100) {
|
|
463
|
+
const res = await withRetry(() => fetchEncodedAccounts(this.rpc, allPdas.slice(i, i + 100), {
|
|
464
|
+
commitment: this.commitment,
|
|
465
|
+
}));
|
|
466
|
+
accounts.push(...res);
|
|
467
|
+
}
|
|
468
|
+
const recordsByMint = await this._recordsByMint(opts?.includeMetadata === true);
|
|
469
|
+
const result = {};
|
|
470
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
471
|
+
const { mint } = pairs[i];
|
|
472
|
+
const configAccount = accounts[i * 2];
|
|
473
|
+
const controllersAccount = accounts[i * 2 + 1];
|
|
474
|
+
if (!configAccount?.exists)
|
|
475
|
+
continue;
|
|
476
|
+
const config = decodeAntConfig(configAccount).data;
|
|
477
|
+
const controllers = controllersAccount?.exists
|
|
478
|
+
? decodeAntControllers(controllersAccount).data.controllers.map((c) => c)
|
|
479
|
+
: [];
|
|
480
|
+
const sorted = recordsByMint.get(mint) ?? {};
|
|
481
|
+
const plainRecords = {};
|
|
482
|
+
for (const [key, val] of Object.entries(sorted)) {
|
|
483
|
+
const { index: _, ...rec } = val;
|
|
484
|
+
plainRecords[key] = rec;
|
|
485
|
+
}
|
|
486
|
+
const owner = config.lastKnownOwner;
|
|
487
|
+
result[mint] = {
|
|
488
|
+
Name: config.name,
|
|
489
|
+
Ticker: config.ticker,
|
|
490
|
+
Description: config.description,
|
|
491
|
+
Keywords: config.keywords,
|
|
492
|
+
Denomination: 0,
|
|
493
|
+
Owner: owner,
|
|
494
|
+
Controllers: controllers,
|
|
495
|
+
Records: plainRecords,
|
|
496
|
+
Balances: { [owner]: 1 },
|
|
497
|
+
Logo: config.logo,
|
|
498
|
+
TotalSupply: 1,
|
|
499
|
+
Initialized: true,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Group every AntRecord (+ optional metadata) in the program by mint via a
|
|
506
|
+
* single `getProgramAccounts` scan (the mint sits at offset 8). Used by
|
|
507
|
+
* {@link getANTStates} to load all ANTs' undername records in one round trip
|
|
508
|
+
* instead of one mint-filtered scan per ANT.
|
|
509
|
+
*/
|
|
510
|
+
async _recordsByMint(includeMetadata) {
|
|
511
|
+
const discFilter = (discriminator) => [
|
|
512
|
+
{
|
|
513
|
+
memcmp: {
|
|
514
|
+
offset: 0n,
|
|
515
|
+
bytes: discriminator,
|
|
516
|
+
encoding: 'base58',
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
const [recordAccounts, metaAccounts] = (await Promise.all([
|
|
521
|
+
withRetry(() => this.rpc
|
|
522
|
+
.getProgramAccounts(this.antProgram, {
|
|
523
|
+
commitment: this.commitment,
|
|
524
|
+
encoding: 'base64',
|
|
525
|
+
filters: discFilter(bs58.encode(ANT_RECORD_DISCRIMINATOR)),
|
|
526
|
+
})
|
|
527
|
+
.send()),
|
|
528
|
+
includeMetadata
|
|
529
|
+
? withRetry(() => this.rpc
|
|
530
|
+
.getProgramAccounts(this.antProgram, {
|
|
531
|
+
commitment: this.commitment,
|
|
532
|
+
encoding: 'base64',
|
|
533
|
+
filters: discFilter(bs58.encode(ANT_RECORD_METADATA_DISCRIMINATOR)),
|
|
534
|
+
})
|
|
535
|
+
.send())
|
|
536
|
+
: Promise.resolve([]),
|
|
537
|
+
]));
|
|
538
|
+
const recordDecoder = getAntRecordDecoder();
|
|
539
|
+
const metaDecoder = getAntRecordMetadataDecoder();
|
|
540
|
+
// Metadata keyed by `${mint}:${undernameHash}` (mint at 8, hash at 40).
|
|
541
|
+
const metaByKey = new Map();
|
|
542
|
+
for (const { account } of metaAccounts) {
|
|
543
|
+
try {
|
|
544
|
+
const buf = Buffer.from(account.data[0], 'base64');
|
|
545
|
+
const mint = bs58.encode(buf.subarray(8, 40));
|
|
546
|
+
const hash = buf.subarray(40, 72).toString('hex');
|
|
547
|
+
metaByKey.set(`${mint}:${hash}`, metaDecoder.decode(new Uint8Array(buf)));
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// Skip malformed
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const byMint = new Map();
|
|
554
|
+
const indexByMint = new Map();
|
|
555
|
+
for (const { account } of recordAccounts) {
|
|
556
|
+
try {
|
|
557
|
+
const buf = Buffer.from(account.data[0], 'base64');
|
|
558
|
+
const mint = bs58.encode(buf.subarray(8, 40));
|
|
559
|
+
const record = recordDecoder.decode(new Uint8Array(buf));
|
|
560
|
+
const hash = __createHash('sha256')
|
|
561
|
+
.update(record.undername.toLowerCase())
|
|
562
|
+
.digest('hex');
|
|
563
|
+
const meta = metaByKey.get(`${mint}:${hash}`);
|
|
564
|
+
const idx = indexByMint.get(mint) ?? 0;
|
|
565
|
+
let bucket = byMint.get(mint);
|
|
566
|
+
if (!bucket) {
|
|
567
|
+
bucket = {};
|
|
568
|
+
byMint.set(mint, bucket);
|
|
569
|
+
}
|
|
570
|
+
bucket[record.undername] = {
|
|
571
|
+
transactionId: record.target,
|
|
572
|
+
targetProtocol: record.targetProtocol,
|
|
573
|
+
ttlSeconds: record.ttlSeconds,
|
|
574
|
+
priority: record.priority?.__option === 'Some'
|
|
575
|
+
? record.priority.value
|
|
576
|
+
: undefined,
|
|
577
|
+
owner: record.owner?.__option === 'Some'
|
|
578
|
+
? record.owner.value
|
|
579
|
+
: undefined,
|
|
580
|
+
displayName: meta?.displayName?.__option === 'Some'
|
|
581
|
+
? meta.displayName.value
|
|
582
|
+
: undefined,
|
|
583
|
+
logo: meta?.recordLogo?.__option === 'Some'
|
|
584
|
+
? meta.recordLogo.value
|
|
585
|
+
: undefined,
|
|
586
|
+
description: meta?.recordDescription?.__option === 'Some'
|
|
587
|
+
? meta.recordDescription.value
|
|
588
|
+
: undefined,
|
|
589
|
+
keywords: meta?.recordKeywords?.__option === 'Some'
|
|
590
|
+
? meta.recordKeywords.value
|
|
591
|
+
: undefined,
|
|
592
|
+
index: idx,
|
|
593
|
+
};
|
|
594
|
+
indexByMint.set(mint, idx + 1);
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
// Skip malformed
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return byMint;
|
|
601
|
+
}
|
|
602
|
+
// =========================================
|
|
603
|
+
// Balance reads (NFT model — owner has balance 1)
|
|
604
|
+
// =========================================
|
|
605
|
+
async getBalance({ address: queryAddress }, _opts) {
|
|
606
|
+
const config = await this.fetchConfig();
|
|
607
|
+
return config.owner === queryAddress ? 1 : 0;
|
|
608
|
+
}
|
|
609
|
+
async getBalances(_opts) {
|
|
610
|
+
const config = await this.fetchConfig();
|
|
611
|
+
return { [config.owner]: 1 };
|
|
612
|
+
}
|
|
613
|
+
// =========================================
|
|
614
|
+
// State / Info composites
|
|
615
|
+
// =========================================
|
|
616
|
+
async getState(opts) {
|
|
617
|
+
const [{ config, controllers }, records] = await Promise.all([
|
|
618
|
+
this._fetchConfigAndControllers(),
|
|
619
|
+
this.getRecords(opts),
|
|
620
|
+
]);
|
|
621
|
+
// Convert SortedANTRecords to ANTRecords (strip index)
|
|
622
|
+
const plainRecords = {};
|
|
623
|
+
for (const [key, val] of Object.entries(records)) {
|
|
624
|
+
const { index: _, ...record } = val;
|
|
625
|
+
plainRecords[key] = record;
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
Name: config.name,
|
|
629
|
+
Ticker: config.ticker,
|
|
630
|
+
Description: config.description,
|
|
631
|
+
Keywords: config.keywords,
|
|
632
|
+
Denomination: 0,
|
|
633
|
+
Owner: config.owner,
|
|
634
|
+
Controllers: controllers,
|
|
635
|
+
Records: plainRecords,
|
|
636
|
+
Balances: { [config.owner]: 1 },
|
|
637
|
+
Logo: config.logo,
|
|
638
|
+
TotalSupply: 1,
|
|
639
|
+
Initialized: true,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
async getInfo(_opts) {
|
|
643
|
+
const config = await this.fetchConfig();
|
|
644
|
+
return {
|
|
645
|
+
Name: config.name,
|
|
646
|
+
Owner: config.owner,
|
|
647
|
+
Ticker: config.ticker,
|
|
648
|
+
'Total-Supply': '1',
|
|
649
|
+
Description: config.description,
|
|
650
|
+
Keywords: config.keywords,
|
|
651
|
+
Logo: config.logo,
|
|
652
|
+
Denomination: '0',
|
|
653
|
+
Handlers: [
|
|
654
|
+
'balance',
|
|
655
|
+
'balances',
|
|
656
|
+
'totalSupply',
|
|
657
|
+
'info',
|
|
658
|
+
'controllers',
|
|
659
|
+
'record',
|
|
660
|
+
'records',
|
|
661
|
+
'state',
|
|
662
|
+
'transfer',
|
|
663
|
+
'addController',
|
|
664
|
+
'removeController',
|
|
665
|
+
'setRecord',
|
|
666
|
+
'removeRecord',
|
|
667
|
+
'setName',
|
|
668
|
+
'setTicker',
|
|
669
|
+
'setDescription',
|
|
670
|
+
'setKeywords',
|
|
671
|
+
'setLogo',
|
|
672
|
+
'initializeState',
|
|
673
|
+
'releaseName',
|
|
674
|
+
'reassignName',
|
|
675
|
+
'approvePrimaryName',
|
|
676
|
+
'removePrimaryNames',
|
|
677
|
+
'transferRecordOwnership',
|
|
678
|
+
'_eval',
|
|
679
|
+
'_default',
|
|
680
|
+
],
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
async getHandlers() {
|
|
684
|
+
// Solana ANT supports all standard handlers
|
|
685
|
+
return [
|
|
686
|
+
'balance',
|
|
687
|
+
'balances',
|
|
688
|
+
'totalSupply',
|
|
689
|
+
'info',
|
|
690
|
+
'controllers',
|
|
691
|
+
'record',
|
|
692
|
+
'records',
|
|
693
|
+
'state',
|
|
694
|
+
'transfer',
|
|
695
|
+
'addController',
|
|
696
|
+
'removeController',
|
|
697
|
+
'setRecord',
|
|
698
|
+
'removeRecord',
|
|
699
|
+
'setName',
|
|
700
|
+
'setTicker',
|
|
701
|
+
'setDescription',
|
|
702
|
+
'setKeywords',
|
|
703
|
+
'setLogo',
|
|
704
|
+
'initializeState',
|
|
705
|
+
'releaseName',
|
|
706
|
+
'reassignName',
|
|
707
|
+
'approvePrimaryName',
|
|
708
|
+
'removePrimaryNames',
|
|
709
|
+
'transferRecordOwnership',
|
|
710
|
+
'_eval',
|
|
711
|
+
'_default',
|
|
712
|
+
];
|
|
713
|
+
}
|
|
714
|
+
async getModuleId(_opts) {
|
|
715
|
+
// Solana programs don't have module IDs — return the program address
|
|
716
|
+
return this.antProgram;
|
|
717
|
+
}
|
|
718
|
+
async getVersion(_opts) {
|
|
719
|
+
return '1.0.0-solana';
|
|
720
|
+
}
|
|
721
|
+
async isLatestVersion(_opts) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|