@ar.io/sdk 3.24.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +682 -600
- 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 +221 -0
- package/lib/esm/cli/commands/gatewayWriteCommands.js +142 -23
- package/lib/esm/cli/commands/pruneCommands.js +150 -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 +280 -174
- package/lib/esm/common/ant-registry.js +17 -143
- package/lib/esm/common/ant.js +44 -1167
- package/lib/esm/common/faucet.js +11 -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 +51 -263
- 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
package/lib/esm/cli/utils.js
CHANGED
|
@@ -14,26 +14,17 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { readFileSync } from 'fs';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
21
|
-
* you may not use this file except in compliance with the License.
|
|
22
|
-
* You may obtain a copy of the License at
|
|
23
|
-
*
|
|
24
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
25
|
-
*
|
|
26
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
27
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
28
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
29
|
-
* See the License for the specific language governing permissions and
|
|
30
|
-
* limitations under the License.
|
|
31
|
-
*/
|
|
32
|
-
import { EthereumSigner } from '@dha-team/arbundles';
|
|
33
|
-
import { connect } from '@permaweb/aoconnect';
|
|
17
|
+
import { address, createKeyPairSignerFromBytes, createSolanaRpcSubscriptions, } from '@solana/kit';
|
|
18
|
+
import bs58 from 'bs58';
|
|
34
19
|
import { program } from 'commander';
|
|
35
20
|
import prompts from 'prompts';
|
|
36
|
-
import {
|
|
21
|
+
import { ANTRegistry } from '../common/ant-registry.js';
|
|
22
|
+
import { ANT } from '../common/ant.js';
|
|
23
|
+
import { ARIO } from '../common/io.js';
|
|
24
|
+
import { Logger } from '../common/logger.js';
|
|
25
|
+
import { createCircuitBreakerRpc, defaultFallbackUrl, } from '../solana/rpc-circuit-breaker.js';
|
|
26
|
+
import { fundFromOptions, isValidFundFrom, isValidIntent, validIntents, } from '../types/io.js';
|
|
27
|
+
import { ARIOToken, mARIOToken } from '../types/token.js';
|
|
37
28
|
import { globalOptions } from './options.js';
|
|
38
29
|
export const defaultTtlSecondsCLI = 3600;
|
|
39
30
|
export function stringifyJsonForCLIDisplay(json) {
|
|
@@ -81,46 +72,6 @@ export function makeCommand({ description, name, options = [], action, }) {
|
|
|
81
72
|
}
|
|
82
73
|
return appliedCommand;
|
|
83
74
|
}
|
|
84
|
-
export function arioProcessIdFromOptions({ arioProcessId, devnet, testnet, }) {
|
|
85
|
-
if (arioProcessId !== undefined) {
|
|
86
|
-
return arioProcessId;
|
|
87
|
-
}
|
|
88
|
-
if (devnet) {
|
|
89
|
-
return ARIO_DEVNET_PROCESS_ID;
|
|
90
|
-
}
|
|
91
|
-
if (testnet) {
|
|
92
|
-
return ARIO_TESTNET_PROCESS_ID;
|
|
93
|
-
}
|
|
94
|
-
return ARIO_MAINNET_PROCESS_ID;
|
|
95
|
-
}
|
|
96
|
-
export function antRegistryIdFromOptions({ antRegistryProcessId, testnet, }) {
|
|
97
|
-
if (antRegistryProcessId !== undefined) {
|
|
98
|
-
return antRegistryProcessId;
|
|
99
|
-
}
|
|
100
|
-
if (testnet) {
|
|
101
|
-
return ANT_REGISTRY_TESTNET_ID;
|
|
102
|
-
}
|
|
103
|
-
return ANT_REGISTRY_ID;
|
|
104
|
-
}
|
|
105
|
-
function walletFromOptions({ privateKey, walletFile, }) {
|
|
106
|
-
if (privateKey !== undefined) {
|
|
107
|
-
return JSON.parse(privateKey);
|
|
108
|
-
}
|
|
109
|
-
if (walletFile !== undefined) {
|
|
110
|
-
return JSON.parse(readFileSync(walletFile, 'utf-8'));
|
|
111
|
-
}
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
export function requiredJwkFromOptions(options) {
|
|
115
|
-
const jwk = walletFromOptions(options);
|
|
116
|
-
if (jwk === undefined) {
|
|
117
|
-
throw new Error('No JWK provided for signing!\nPlease provide a stringified JWK with `--private-key` or the file path of a jwk.json file with `--wallet-file`');
|
|
118
|
-
}
|
|
119
|
-
return jwk;
|
|
120
|
-
}
|
|
121
|
-
export function jwkToAddress(jwk) {
|
|
122
|
-
return sha256B64Url(fromB64Url(jwk.n));
|
|
123
|
-
}
|
|
124
75
|
function setLoggerIfDebug(options) {
|
|
125
76
|
if (options.debug) {
|
|
126
77
|
Logger.default.setLogLevel('debug');
|
|
@@ -130,68 +81,107 @@ export function getLoggerFromOptions(options) {
|
|
|
130
81
|
setLoggerIfDebug(options);
|
|
131
82
|
return Logger.default;
|
|
132
83
|
}
|
|
133
|
-
function aoProcessFromOptions(options) {
|
|
134
|
-
return new AOProcess({
|
|
135
|
-
processId: arioProcessIdFromOptions(options),
|
|
136
|
-
ao: connect({
|
|
137
|
-
MODE: 'legacy',
|
|
138
|
-
CU_URL: options.cuUrl,
|
|
139
|
-
}),
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
84
|
export function readARIOFromOptions(options) {
|
|
143
85
|
setLoggerIfDebug(options);
|
|
86
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
144
87
|
return ARIO.init({
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
88
|
+
rpc: createCliRpc(rpcUrl),
|
|
89
|
+
...(options.coreProgramId
|
|
90
|
+
? { coreProgramId: address(options.coreProgramId) }
|
|
91
|
+
: {}),
|
|
92
|
+
...(options.garProgramId
|
|
93
|
+
? { garProgramId: address(options.garProgramId) }
|
|
94
|
+
: {}),
|
|
95
|
+
...(options.arnsProgramId
|
|
96
|
+
? { arnsProgramId: address(options.arnsProgramId) }
|
|
97
|
+
: {}),
|
|
151
98
|
});
|
|
152
99
|
}
|
|
153
|
-
export function readANTRegistryFromOptions(options) {
|
|
100
|
+
export async function readANTRegistryFromOptions(options) {
|
|
101
|
+
setLoggerIfDebug(options);
|
|
102
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
154
103
|
return ANTRegistry.init({
|
|
155
|
-
|
|
156
|
-
|
|
104
|
+
rpc: createCliRpc(rpcUrl),
|
|
105
|
+
...(options.antProgramId
|
|
106
|
+
? { antProgramId: address(options.antProgramId) }
|
|
107
|
+
: {}),
|
|
157
108
|
});
|
|
158
109
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
110
|
+
/** Derive a WS URL from an HTTP/HTTPS RPC URL by swapping the scheme. */
|
|
111
|
+
function wsUrlFromRpcUrl(rpcUrl) {
|
|
112
|
+
// Surfpool and `solana-test-validator` follow Solana's well-known localhost
|
|
113
|
+
// convention: HTTP RPC on 8899 and WebSocket on 8900 (a separate port).
|
|
114
|
+
// Public RPCs (mainnet/devnet/testnet) put both on the same port (443/443),
|
|
115
|
+
// so a naive `http→ws` swap works there but breaks against any local
|
|
116
|
+
// validator. Bump the port iff we recognise the localhost+8899 pair.
|
|
117
|
+
let url;
|
|
118
|
+
try {
|
|
119
|
+
url = new URL(rpcUrl);
|
|
163
120
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const signer = new EthereumSigner(wallet);
|
|
167
|
-
// For EthereumSigner, we need to convert the JWK to a string
|
|
168
|
-
return { signer, signerAddress: signer.publicKey.toString('hex') };
|
|
121
|
+
catch {
|
|
122
|
+
return rpcUrl.replace(/^http/, 'ws');
|
|
169
123
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const contractSigner = contractSignerFromOptions(options);
|
|
176
|
-
if (contractSigner === undefined) {
|
|
177
|
-
throw new Error('No signer provided for signing!\nPlease provide a stringified JWK or Ethereum private key with `--private-key` or the file path of an arweave.jwk.json or eth.private.key.txt file with `--wallet-file`');
|
|
124
|
+
const isLocalhost = url.hostname === 'localhost' ||
|
|
125
|
+
url.hostname === '127.0.0.1' ||
|
|
126
|
+
url.hostname === '0.0.0.0';
|
|
127
|
+
if (isLocalhost && url.port === '8899') {
|
|
128
|
+
url.port = '8900';
|
|
178
129
|
}
|
|
179
|
-
|
|
130
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
131
|
+
return url.toString().replace(/\/$/, '');
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a {@link SolanaRpc} wrapped with a circuit-breaker that falls back to
|
|
135
|
+
* the cluster's public RPC when the primary endpoint becomes unhealthy.
|
|
136
|
+
*/
|
|
137
|
+
function createCliRpc(rpcUrl) {
|
|
138
|
+
return createCircuitBreakerRpc({
|
|
139
|
+
primaryUrl: rpcUrl,
|
|
140
|
+
fallbackUrl: defaultFallbackUrl(rpcUrl),
|
|
141
|
+
});
|
|
180
142
|
}
|
|
181
|
-
|
|
182
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Load a Solana KeyPairSigner from --private-key (base58) or --wallet-file
|
|
145
|
+
* (JSON array of bytes). Throws with a helpful message if neither is set.
|
|
146
|
+
*/
|
|
147
|
+
async function loadSolanaSignerFromOptions(options) {
|
|
148
|
+
let secretKey;
|
|
149
|
+
if (options.privateKey) {
|
|
150
|
+
secretKey = bs58.decode(options.privateKey);
|
|
151
|
+
}
|
|
152
|
+
else if (options.walletFile) {
|
|
153
|
+
const raw = readFileSync(options.walletFile, 'utf-8');
|
|
154
|
+
secretKey = new Uint8Array(JSON.parse(raw));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw new Error('Solana write operations require a signer.\n' +
|
|
158
|
+
'Provide a Solana keypair with --wallet-file <path-to-keypair.json> ' +
|
|
159
|
+
'or --private-key <base58-encoded-key>');
|
|
160
|
+
}
|
|
161
|
+
return createKeyPairSignerFromBytes(secretKey);
|
|
183
162
|
}
|
|
184
|
-
export function writeARIOFromOptions(options) {
|
|
185
|
-
const { signer, signerAddress } = requiredContractSignerFromOptions(options);
|
|
163
|
+
export async function writeARIOFromOptions(options) {
|
|
186
164
|
setLoggerIfDebug(options);
|
|
165
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
166
|
+
const signer = await loadSolanaSignerFromOptions(options);
|
|
187
167
|
return {
|
|
188
168
|
ario: ARIO.init({
|
|
189
|
-
|
|
169
|
+
rpc: createCliRpc(rpcUrl),
|
|
170
|
+
rpcSubscriptions: createSolanaRpcSubscriptions(wsUrlFromRpcUrl(rpcUrl)),
|
|
190
171
|
signer,
|
|
191
|
-
|
|
192
|
-
|
|
172
|
+
// Forward program-id overrides so localnet / devnet writes target the
|
|
173
|
+
// deployed program IDs instead of falling back to mainnet defaults.
|
|
174
|
+
...(options.coreProgramId
|
|
175
|
+
? { coreProgramId: address(options.coreProgramId) }
|
|
176
|
+
: {}),
|
|
177
|
+
...(options.garProgramId
|
|
178
|
+
? { garProgramId: address(options.garProgramId) }
|
|
179
|
+
: {}),
|
|
180
|
+
...(options.arnsProgramId
|
|
181
|
+
? { arnsProgramId: address(options.arnsProgramId) }
|
|
182
|
+
: {}),
|
|
193
183
|
}),
|
|
194
|
-
signerAddress,
|
|
184
|
+
signerAddress: signer.address,
|
|
195
185
|
};
|
|
196
186
|
}
|
|
197
187
|
export function formatARIOWithCommas(value) {
|
|
@@ -205,14 +195,51 @@ export function formatARIOWithCommas(value) {
|
|
|
205
195
|
export function formatMARIOToARIOWithCommas(value) {
|
|
206
196
|
return formatARIOWithCommas(value.toARIO());
|
|
207
197
|
}
|
|
208
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Resolve the wallet address from CLI options. Priority order:
|
|
200
|
+
* 1. `--address <addr>` — used verbatim.
|
|
201
|
+
* 2. `--wallet-file <path-to-keypair.json>` — derive the Solana pubkey
|
|
202
|
+
* from the keypair file (last 32 bytes of the 64-byte secret-key
|
|
203
|
+
* array are the public key; base58-encode them).
|
|
204
|
+
* 3. `--private-key <base58-secret>` — base58-decode the secret, take
|
|
205
|
+
* the trailing 32 bytes as the pubkey. Matches the keypair-file
|
|
206
|
+
* path; kept aligned with `loadSolanaSignerFromOptions` so cost
|
|
207
|
+
* / funding lookups work even when the user authenticates with
|
|
208
|
+
* only a private key.
|
|
209
|
+
*
|
|
210
|
+
* Returns `undefined` when none of those is set. Use
|
|
211
|
+
* `requiredAddressFromOptions` for callers that must have an address.
|
|
212
|
+
*/
|
|
209
213
|
export function addressFromOptions(options) {
|
|
210
214
|
if (options.address !== undefined) {
|
|
211
215
|
return options.address;
|
|
212
216
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
217
|
+
if (options.walletFile !== undefined) {
|
|
218
|
+
try {
|
|
219
|
+
const raw = readFileSync(options.walletFile, 'utf-8');
|
|
220
|
+
const bytes = new Uint8Array(JSON.parse(raw));
|
|
221
|
+
// Solana keypair JSON files are a 64-byte Uint8Array: first 32 bytes
|
|
222
|
+
// are the seed/secret, last 32 bytes are the Ed25519 public key.
|
|
223
|
+
if (bytes.length !== 64) {
|
|
224
|
+
throw new Error(`Wallet file is ${bytes.length} bytes — expected 64-byte Solana keypair JSON`);
|
|
225
|
+
}
|
|
226
|
+
return bs58.encode(bytes.slice(32));
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
throw new Error(`Failed to read Solana pubkey from --wallet-file '${options.walletFile}': ${err.message}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (options.privateKey !== undefined) {
|
|
233
|
+
try {
|
|
234
|
+
const bytes = bs58.decode(options.privateKey);
|
|
235
|
+
if (bytes.length !== 64) {
|
|
236
|
+
throw new Error(`Decoded private key is ${bytes.length} bytes — expected 64 (32-byte secret + 32-byte pubkey)`);
|
|
237
|
+
}
|
|
238
|
+
return bs58.encode(bytes.slice(32));
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
throw new Error(`Failed to derive Solana pubkey from --private-key: ${err.message}`);
|
|
242
|
+
}
|
|
216
243
|
}
|
|
217
244
|
return undefined;
|
|
218
245
|
}
|
|
@@ -303,44 +330,8 @@ export function customTagsFromOptions(options) {
|
|
|
303
330
|
tags,
|
|
304
331
|
};
|
|
305
332
|
}
|
|
306
|
-
export function servicesFromOptions(services) {
|
|
307
|
-
if (services === undefined || services === null || services === '') {
|
|
308
|
-
return undefined;
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
const parsed = JSON.parse(services);
|
|
312
|
-
// Validate structure
|
|
313
|
-
if (!parsed.bundlers || !Array.isArray(parsed.bundlers)) {
|
|
314
|
-
throw new Error('Services must have a "bundlers" array');
|
|
315
|
-
}
|
|
316
|
-
if (parsed.bundlers.length > 20) {
|
|
317
|
-
throw new Error('Maximum 20 bundlers allowed');
|
|
318
|
-
}
|
|
319
|
-
// Validate each bundler
|
|
320
|
-
for (const bundler of parsed.bundlers) {
|
|
321
|
-
if (!bundler.fqdn || typeof bundler.fqdn !== 'string') {
|
|
322
|
-
throw new Error('Each bundler must have a valid "fqdn" string');
|
|
323
|
-
}
|
|
324
|
-
if (typeof bundler.port !== 'number' ||
|
|
325
|
-
bundler.port < 0 ||
|
|
326
|
-
bundler.port > 65535) {
|
|
327
|
-
throw new Error('Each bundler must have a valid "port" (0-65535)');
|
|
328
|
-
}
|
|
329
|
-
if (bundler.protocol !== 'https') {
|
|
330
|
-
throw new Error('Each bundler protocol must be "https"');
|
|
331
|
-
}
|
|
332
|
-
if (!bundler.path || typeof bundler.path !== 'string') {
|
|
333
|
-
throw new Error('Each bundler must have a valid "path" string');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return parsed;
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
throw new Error(`Invalid services JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
333
|
export function gatewaySettingsFromOptions(options) {
|
|
343
|
-
const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates,
|
|
334
|
+
const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, } = options;
|
|
344
335
|
return {
|
|
345
336
|
observerAddress,
|
|
346
337
|
allowDelegatedStaking,
|
|
@@ -355,7 +346,6 @@ export function gatewaySettingsFromOptions(options) {
|
|
|
355
346
|
note,
|
|
356
347
|
port: port !== undefined ? +port : undefined,
|
|
357
348
|
properties,
|
|
358
|
-
services: servicesFromOptions(services),
|
|
359
349
|
};
|
|
360
350
|
}
|
|
361
351
|
export function requiredTargetAndQuantityFromOptions(options) {
|
|
@@ -444,27 +434,27 @@ export function requiredProcessIdFromOptions(o) {
|
|
|
444
434
|
}
|
|
445
435
|
return o.processId;
|
|
446
436
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
processId: requiredProcessIdFromOptions(options),
|
|
450
|
-
ao: connect({
|
|
451
|
-
MODE: 'legacy',
|
|
452
|
-
CU_URL: options.cuUrl,
|
|
453
|
-
}),
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
export function readANTFromOptions(options) {
|
|
437
|
+
export async function readANTFromOptions(options) {
|
|
438
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
457
439
|
return ANT.init({
|
|
458
|
-
|
|
459
|
-
|
|
440
|
+
processId: requiredProcessIdFromOptions(options),
|
|
441
|
+
rpc: createCliRpc(rpcUrl),
|
|
442
|
+
...(options.antProgramId
|
|
443
|
+
? { antProgramId: address(options.antProgramId) }
|
|
444
|
+
: {}),
|
|
460
445
|
});
|
|
461
446
|
}
|
|
462
|
-
export function writeANTFromOptions(options
|
|
463
|
-
|
|
447
|
+
export async function writeANTFromOptions(options) {
|
|
448
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
449
|
+
const kitSigner = await loadSolanaSignerFromOptions(options);
|
|
464
450
|
return ANT.init({
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
451
|
+
processId: requiredProcessIdFromOptions(options),
|
|
452
|
+
rpc: createCliRpc(rpcUrl),
|
|
453
|
+
rpcSubscriptions: createSolanaRpcSubscriptions(wsUrlFromRpcUrl(rpcUrl)),
|
|
454
|
+
signer: kitSigner,
|
|
455
|
+
...(options.antProgramId
|
|
456
|
+
? { antProgramId: address(options.antProgramId) }
|
|
457
|
+
: {}),
|
|
468
458
|
});
|
|
469
459
|
}
|
|
470
460
|
export function booleanFromOptions(options, key) {
|
|
@@ -512,19 +502,55 @@ export function requiredPositiveIntegerFromOptions(options, key) {
|
|
|
512
502
|
}
|
|
513
503
|
return value;
|
|
514
504
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Spawn a fresh ANT on Solana from CLI options.
|
|
507
|
+
*
|
|
508
|
+
* Resolves the signer the same way `writeARIOFromOptions` does (KeyPairSigner
|
|
509
|
+
* from `--wallet-file` or `--private-key`), then bundles MPL Core `CreateV1` +
|
|
510
|
+
* `ario_ant::initialize` into a single transaction. The signer's address
|
|
511
|
+
* becomes the ANT owner on chain — no separate `--address` is required.
|
|
512
|
+
*
|
|
513
|
+
* Maps the user-facing options (`--name`, `--ticker`, `--description`,
|
|
514
|
+
* `--keywords`, `--logo`, `--target` for the @ record tx id) onto the Solana
|
|
515
|
+
* `InitializeAntParams` payload.
|
|
516
|
+
*/
|
|
517
|
+
export async function spawnSolanaANTFromOptions(options) {
|
|
518
|
+
setLoggerIfDebug(options);
|
|
519
|
+
const { spawnSolanaANT } = await import('../solana/spawn-ant.js');
|
|
520
|
+
const rpcUrl = options.rpcUrl ?? 'https://api.mainnet-beta.solana.com';
|
|
521
|
+
const kitSigner = await loadSolanaSignerFromOptions(options);
|
|
522
|
+
const name = options.name ?? `ANT-${kitSigner.address.slice(0, 8)}`;
|
|
523
|
+
// ANT NFT metadata URI — required so the asset renders correctly in
|
|
524
|
+
// marketplaces. Caller must upload a JSON metadata file (build via
|
|
525
|
+
// `buildAntMetadata` from `@ar.io/sdk/solana`, host on Arweave via Turbo
|
|
526
|
+
// or any other gateway) and pass the resulting URI here.
|
|
527
|
+
const metadataUri = options.metadataUri;
|
|
528
|
+
if (!metadataUri || typeof metadataUri !== 'string') {
|
|
529
|
+
throw new Error('spawn-ant: --metadata-uri is required.\n\n' +
|
|
530
|
+
'Build the JSON metadata with `buildAntMetadata` from `@ar.io/sdk/solana`,\n' +
|
|
531
|
+
'upload it to Arweave (free for files under 100 KiB via @ardrive/turbo-sdk\n' +
|
|
532
|
+
'with a `HexSolanaSigner`), then pass the resulting URI:\n' +
|
|
533
|
+
' --metadata-uri "ar://<txid>" (canonical AR.IO scheme)\n' +
|
|
534
|
+
' --metadata-uri "https://<gateway>/raw/<txid>" (immediate render)\n\n' +
|
|
535
|
+
'See sdk/scripts/devnet-validation/populate-ant.ts for an end-to-end example.');
|
|
536
|
+
}
|
|
537
|
+
return spawnSolanaANT({
|
|
538
|
+
rpc: createCliRpc(rpcUrl),
|
|
539
|
+
rpcSubscriptions: createSolanaRpcSubscriptions(wsUrlFromRpcUrl(rpcUrl)),
|
|
540
|
+
signer: kitSigner,
|
|
541
|
+
state: {
|
|
542
|
+
name,
|
|
543
|
+
uri: metadataUri,
|
|
544
|
+
ticker: options.ticker,
|
|
545
|
+
description: options.description,
|
|
546
|
+
keywords: options.keywords,
|
|
547
|
+
logo: options.logo,
|
|
548
|
+
// `--target` carries the @ record's tx id for the initial record.
|
|
549
|
+
transactionId: options.target,
|
|
550
|
+
},
|
|
551
|
+
...(options.antProgramId
|
|
552
|
+
? { antProgramId: address(options.antProgramId) }
|
|
553
|
+
: {}),
|
|
528
554
|
});
|
|
529
555
|
}
|
|
530
556
|
export function getTokenCostParamsFromOptions(o) {
|
|
@@ -557,6 +583,86 @@ export function fundFromFromOptions(o) {
|
|
|
557
583
|
export function referrerFromOptions(o) {
|
|
558
584
|
return o.referrer;
|
|
559
585
|
}
|
|
586
|
+
/** Parse `--withdrawal-id` from CLI options into a bigint. */
|
|
587
|
+
export function withdrawalIdFromOptions(o) {
|
|
588
|
+
if (o.withdrawalId === undefined)
|
|
589
|
+
return undefined;
|
|
590
|
+
try {
|
|
591
|
+
return BigInt(o.withdrawalId);
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
throw new Error(`Invalid --withdrawal-id: '${o.withdrawalId}' is not a valid u64 integer`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Parse `--funding-plan-json` into a `FundingSourceSpec[]`. Validates each
|
|
599
|
+
* entry's `kind` against the on-chain enum and parses `amount` as a bigint.
|
|
600
|
+
* Each Delegation/OperatorStake entry MAY carry a `gateway` field (base58
|
|
601
|
+
* Solana address) — required when the plan spans multiple gateways.
|
|
602
|
+
*
|
|
603
|
+
* Format examples:
|
|
604
|
+
* '[{"kind":"balance","amount":"100"},{"kind":"withdrawal","amount":"500"}]'
|
|
605
|
+
* '[{"kind":"delegation","amount":"100","gateway":"Gw1..."},{"kind":"delegation","amount":"50","gateway":"Gw2..."}]'
|
|
606
|
+
*
|
|
607
|
+
* Returns undefined when the flag isn't set; throws Error on malformed JSON
|
|
608
|
+
* or unknown kinds.
|
|
609
|
+
*/
|
|
610
|
+
export function fundingPlanFromOptions(o) {
|
|
611
|
+
if (!o.fundingPlanJson)
|
|
612
|
+
return undefined;
|
|
613
|
+
let parsed;
|
|
614
|
+
try {
|
|
615
|
+
parsed = JSON.parse(o.fundingPlanJson);
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
throw new Error(`--funding-plan-json is not valid JSON: ${err.message}`);
|
|
619
|
+
}
|
|
620
|
+
if (!Array.isArray(parsed)) {
|
|
621
|
+
throw new Error('--funding-plan-json must be a JSON array');
|
|
622
|
+
}
|
|
623
|
+
const validKinds = new Set([
|
|
624
|
+
'balance',
|
|
625
|
+
'delegation',
|
|
626
|
+
'operatorStake',
|
|
627
|
+
'withdrawal',
|
|
628
|
+
]);
|
|
629
|
+
return parsed.map((entry, idx) => {
|
|
630
|
+
if (typeof entry !== 'object' ||
|
|
631
|
+
entry === null ||
|
|
632
|
+
typeof entry.kind !== 'string' ||
|
|
633
|
+
typeof entry.amount !== 'string') {
|
|
634
|
+
throw new Error(`--funding-plan-json[${idx}] must be { kind: string, amount: string, gateway?: string }`);
|
|
635
|
+
}
|
|
636
|
+
const e = entry;
|
|
637
|
+
if (!validKinds.has(e.kind)) {
|
|
638
|
+
throw new Error(`--funding-plan-json[${idx}].kind must be one of ${[...validKinds].join(', ')} (got '${e.kind}')`);
|
|
639
|
+
}
|
|
640
|
+
let amount;
|
|
641
|
+
try {
|
|
642
|
+
amount = BigInt(e.amount);
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
throw new Error(`--funding-plan-json[${idx}].amount '${e.amount}' is not a valid u64`);
|
|
646
|
+
}
|
|
647
|
+
if (amount <= 0n) {
|
|
648
|
+
throw new Error(`--funding-plan-json[${idx}].amount must be > 0 (got ${e.amount})`);
|
|
649
|
+
}
|
|
650
|
+
const out = {
|
|
651
|
+
kind: e.kind,
|
|
652
|
+
amount,
|
|
653
|
+
};
|
|
654
|
+
if (e.gateway !== undefined) {
|
|
655
|
+
if (typeof e.gateway !== 'string') {
|
|
656
|
+
throw new Error(`--funding-plan-json[${idx}].gateway must be a base58 Solana address`);
|
|
657
|
+
}
|
|
658
|
+
if (e.kind !== 'delegation' && e.kind !== 'operatorStake') {
|
|
659
|
+
throw new Error(`--funding-plan-json[${idx}].gateway is only valid for kind 'delegation' or 'operatorStake' (got '${e.kind}')`);
|
|
660
|
+
}
|
|
661
|
+
out.gateway = e.gateway;
|
|
662
|
+
}
|
|
663
|
+
return out;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
560
666
|
export function assertLockLengthInRange(lockLengthMs, assertMin = true) {
|
|
561
667
|
const minLockLengthMs = 1209600000; // 14 days
|
|
562
668
|
const maxLockLengthMs = 378432000000; // ~12 years
|