@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.
Files changed (169) hide show
  1. package/README.md +757 -589
  2. package/lib/esm/cli/cli.js +188 -152
  3. package/lib/esm/cli/commands/antCommands.js +23 -58
  4. package/lib/esm/cli/commands/arnsPurchaseCommands.js +48 -30
  5. package/lib/esm/cli/commands/escrowCommands.js +227 -0
  6. package/lib/esm/cli/commands/gatewayWriteCommands.js +140 -23
  7. package/lib/esm/cli/commands/pruneCommands.js +154 -0
  8. package/lib/esm/cli/commands/readCommands.js +22 -3
  9. package/lib/esm/cli/commands/transfer.js +6 -6
  10. package/lib/esm/cli/options.js +124 -58
  11. package/lib/esm/cli/utils.js +303 -175
  12. package/lib/esm/common/ant-registry.js +17 -143
  13. package/lib/esm/common/ant.js +44 -1167
  14. package/lib/esm/common/faucet.js +17 -6
  15. package/lib/esm/common/index.js +0 -4
  16. package/lib/esm/common/io.js +25 -1412
  17. package/lib/esm/constants.js +13 -19
  18. package/lib/esm/solana/ant-readable.js +724 -0
  19. package/lib/esm/solana/ant-registry-readable.js +133 -0
  20. package/lib/esm/solana/ant-registry-writeable.js +472 -0
  21. package/lib/esm/solana/ant-writeable.js +384 -0
  22. package/lib/esm/solana/ata.js +70 -0
  23. package/lib/esm/solana/canonical-message.js +128 -0
  24. package/lib/esm/solana/clusters.js +111 -0
  25. package/lib/esm/solana/constants.js +146 -0
  26. package/lib/esm/solana/delegation-math.js +112 -0
  27. package/lib/esm/solana/deserialize.js +711 -0
  28. package/lib/esm/solana/escrow.js +839 -0
  29. package/lib/{cjs/utils/json.js → esm/solana/events.js} +15 -10
  30. package/lib/esm/solana/funding-plan.js +699 -0
  31. package/lib/esm/solana/index.js +126 -0
  32. package/lib/esm/solana/instruction.js +39 -0
  33. package/lib/esm/solana/io-readable.js +2182 -0
  34. package/lib/esm/solana/io-writeable.js +3196 -0
  35. package/lib/esm/solana/json-rpc.js +90 -0
  36. package/lib/esm/solana/metadata.js +81 -0
  37. package/lib/esm/solana/mpl-core.js +192 -0
  38. package/lib/esm/solana/pda.js +332 -0
  39. package/lib/esm/solana/predict-prescribed-observers.js +110 -0
  40. package/lib/esm/solana/retry.js +117 -0
  41. package/lib/esm/solana/rpc-circuit-breaker.js +258 -0
  42. package/lib/esm/solana/send.js +372 -0
  43. package/lib/esm/solana/spawn-ant.js +224 -0
  44. package/lib/esm/solana/types.js +1 -0
  45. package/lib/esm/types/ant.js +27 -15
  46. package/lib/esm/types/io.js +8 -11
  47. package/lib/esm/utils/ant.js +0 -63
  48. package/lib/esm/utils/index.js +0 -3
  49. package/lib/esm/version.js +1 -1
  50. package/lib/types/cli/commands/antCommands.d.ts +5 -13
  51. package/lib/types/cli/commands/arnsPurchaseCommands.d.ts +33 -7
  52. package/lib/types/cli/commands/escrowCommands.d.ts +68 -0
  53. package/lib/types/cli/commands/gatewayWriteCommands.d.ts +12 -11
  54. package/lib/types/cli/commands/pruneCommands.d.ts +31 -0
  55. package/lib/types/cli/commands/readCommands.d.ts +27 -22
  56. package/lib/types/cli/commands/transfer.d.ts +9 -9
  57. package/lib/types/cli/options.d.ts +76 -21
  58. package/lib/types/cli/types.d.ts +11 -13
  59. package/lib/types/cli/utils.d.ts +71 -31
  60. package/lib/types/common/ant-registry.d.ts +49 -47
  61. package/lib/types/common/ant.d.ts +54 -539
  62. package/lib/types/common/faucet.d.ts +20 -8
  63. package/lib/types/common/index.d.ts +0 -3
  64. package/lib/types/common/io.d.ts +66 -258
  65. package/lib/types/constants.d.ts +11 -18
  66. package/lib/types/solana/ant-readable.d.ts +180 -0
  67. package/lib/types/solana/ant-registry-readable.d.ts +105 -0
  68. package/lib/types/solana/ant-registry-writeable.d.ts +249 -0
  69. package/lib/types/solana/ant-writeable.d.ts +177 -0
  70. package/lib/types/solana/ata.d.ts +44 -0
  71. package/lib/types/solana/canonical-message.d.ts +121 -0
  72. package/lib/types/solana/clusters.d.ts +109 -0
  73. package/lib/types/solana/constants.d.ts +119 -0
  74. package/lib/types/solana/delegation-math.d.ts +45 -0
  75. package/lib/types/solana/deserialize.d.ts +262 -0
  76. package/lib/types/solana/escrow.d.ts +480 -0
  77. package/lib/types/solana/events.d.ts +38 -0
  78. package/lib/types/solana/funding-plan.d.ts +225 -0
  79. package/lib/types/solana/index.d.ts +87 -0
  80. package/lib/types/solana/instruction.d.ts +39 -0
  81. package/lib/types/solana/io-readable.d.ts +499 -0
  82. package/lib/types/solana/io-writeable.d.ts +893 -0
  83. package/lib/types/solana/json-rpc.d.ts +47 -0
  84. package/lib/types/solana/metadata.d.ts +84 -0
  85. package/lib/types/solana/mpl-core.d.ts +120 -0
  86. package/lib/types/solana/pda.d.ts +95 -0
  87. package/lib/types/solana/predict-prescribed-observers.d.ts +28 -0
  88. package/lib/types/solana/retry.d.ts +62 -0
  89. package/lib/types/solana/rpc-circuit-breaker.d.ts +78 -0
  90. package/lib/types/solana/send.d.ts +94 -0
  91. package/lib/types/solana/spawn-ant.d.ts +145 -0
  92. package/lib/types/solana/types.d.ts +82 -0
  93. package/lib/types/types/ant-registry.d.ts +43 -4
  94. package/lib/types/types/ant.d.ts +114 -96
  95. package/lib/types/types/common.d.ts +18 -74
  96. package/lib/types/types/faucet.d.ts +2 -2
  97. package/lib/types/types/io.d.ts +244 -158
  98. package/lib/types/types/token.d.ts +0 -12
  99. package/lib/types/utils/ant.d.ts +1 -12
  100. package/lib/types/utils/index.d.ts +0 -3
  101. package/lib/types/version.d.ts +1 -1
  102. package/package.json +36 -33
  103. package/lib/cjs/cli/cli.js +0 -822
  104. package/lib/cjs/cli/commands/antCommands.js +0 -113
  105. package/lib/cjs/cli/commands/arnsPurchaseCommands.js +0 -212
  106. package/lib/cjs/cli/commands/gatewayWriteCommands.js +0 -210
  107. package/lib/cjs/cli/commands/readCommands.js +0 -215
  108. package/lib/cjs/cli/commands/transfer.js +0 -159
  109. package/lib/cjs/cli/options.js +0 -470
  110. package/lib/cjs/cli/types.js +0 -2
  111. package/lib/cjs/cli/utils.js +0 -639
  112. package/lib/cjs/common/ant-registry.js +0 -155
  113. package/lib/cjs/common/ant-versions.js +0 -93
  114. package/lib/cjs/common/ant.js +0 -1182
  115. package/lib/cjs/common/arweave.js +0 -27
  116. package/lib/cjs/common/contracts/ao-process.js +0 -224
  117. package/lib/cjs/common/error.js +0 -64
  118. package/lib/cjs/common/faucet.js +0 -150
  119. package/lib/cjs/common/hyperbeam/hb.js +0 -173
  120. package/lib/cjs/common/index.js +0 -42
  121. package/lib/cjs/common/io.js +0 -1423
  122. package/lib/cjs/common/logger.js +0 -83
  123. package/lib/cjs/common/loggers/winston.js +0 -68
  124. package/lib/cjs/common/marketplace.js +0 -731
  125. package/lib/cjs/common/turbo.js +0 -223
  126. package/lib/cjs/constants.js +0 -41
  127. package/lib/cjs/node/index.js +0 -39
  128. package/lib/cjs/package.json +0 -1
  129. package/lib/cjs/types/ant-registry.js +0 -2
  130. package/lib/cjs/types/ant.js +0 -168
  131. package/lib/cjs/types/common.js +0 -2
  132. package/lib/cjs/types/faucet.js +0 -2
  133. package/lib/cjs/types/index.js +0 -37
  134. package/lib/cjs/types/io.js +0 -51
  135. package/lib/cjs/types/token.js +0 -116
  136. package/lib/cjs/utils/ant.js +0 -108
  137. package/lib/cjs/utils/ao.js +0 -432
  138. package/lib/cjs/utils/arweave.js +0 -285
  139. package/lib/cjs/utils/base64.js +0 -62
  140. package/lib/cjs/utils/hash.js +0 -56
  141. package/lib/cjs/utils/index.js +0 -38
  142. package/lib/cjs/utils/processes.js +0 -173
  143. package/lib/cjs/utils/random.js +0 -30
  144. package/lib/cjs/utils/schema.js +0 -15
  145. package/lib/cjs/utils/url.js +0 -37
  146. package/lib/cjs/version.js +0 -20
  147. package/lib/cjs/web/index.js +0 -41
  148. package/lib/esm/common/ant-versions.js +0 -87
  149. package/lib/esm/common/arweave.js +0 -21
  150. package/lib/esm/common/contracts/ao-process.js +0 -220
  151. package/lib/esm/common/hyperbeam/hb.js +0 -169
  152. package/lib/esm/common/marketplace.js +0 -724
  153. package/lib/esm/common/turbo.js +0 -215
  154. package/lib/esm/node/index.js +0 -20
  155. package/lib/esm/utils/ao.js +0 -420
  156. package/lib/esm/utils/arweave.js +0 -271
  157. package/lib/esm/utils/processes.js +0 -167
  158. package/lib/esm/web/index.js +0 -20
  159. package/lib/types/common/ant-versions.d.ts +0 -39
  160. package/lib/types/common/arweave.d.ts +0 -17
  161. package/lib/types/common/contracts/ao-process.d.ts +0 -47
  162. package/lib/types/common/hyperbeam/hb.d.ts +0 -88
  163. package/lib/types/common/marketplace.d.ts +0 -568
  164. package/lib/types/common/turbo.d.ts +0 -61
  165. package/lib/types/node/index.d.ts +0 -20
  166. package/lib/types/utils/ao.d.ts +0 -80
  167. package/lib/types/utils/arweave.d.ts +0 -79
  168. package/lib/types/utils/processes.d.ts +0 -39
  169. package/lib/types/web/index.d.ts +0 -20
@@ -14,26 +14,17 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { readFileSync } from 'fs';
17
- /**
18
- * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
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 { ANT, ANTRegistry, ANT_REGISTRY_ID, ANT_REGISTRY_TESTNET_ID, AOProcess, ARIO, ARIOToken, ARIO_DEVNET_PROCESS_ID, ARIO_MAINNET_PROCESS_ID, ARIO_TESTNET_PROCESS_ID, ArweaveSigner, DEFAULT_CU_URL, Logger, createAoSigner, fromB64Url, fundFromOptions, initANTStateForAddress, isValidFundFrom, isValidIntent, mARIOToken, sha256B64Url, validIntents, } from '../node/index.js';
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
- hyperbeamUrl: options.hyperbeamUrl,
146
- process: aoProcessFromOptions({
147
- cuUrl: DEFAULT_CU_URL,
148
- ...options,
149
- }),
150
- paymentUrl: options.paymentUrl,
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
- process: aoProcessFromOptions(options),
156
- hyperbeamUrl: options.hyperbeamUrl,
104
+ rpc: createCliRpc(rpcUrl),
105
+ ...(options.antProgramId
106
+ ? { antProgramId: address(options.antProgramId) }
107
+ : {}),
157
108
  });
158
109
  }
159
- export function contractSignerFromOptions(options) {
160
- const wallet = walletFromOptions(options);
161
- if (wallet === undefined) {
162
- return undefined;
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
- const token = options.token ?? 'arweave';
165
- if (token === 'ethereum') {
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
- // TODO: Support other wallet types
171
- const signer = new ArweaveSigner(wallet);
172
- return { signer, signerAddress: jwkToAddress(wallet) };
173
- }
174
- export function requiredContractSignerFromOptions(options) {
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
- return contractSigner;
130
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
131
+ return url.toString().replace(/\/$/, '');
180
132
  }
181
- export function requiredAoSignerFromOptions(options) {
182
- return createAoSigner(requiredContractSignerFromOptions(options).signer);
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
+ });
183
142
  }
184
- export function writeARIOFromOptions(options) {
185
- const { signer, signerAddress } = requiredContractSignerFromOptions(options);
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);
162
+ }
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
- process: aoProcessFromOptions(options),
169
+ rpc: createCliRpc(rpcUrl),
170
+ rpcSubscriptions: createSolanaRpcSubscriptions(wsUrlFromRpcUrl(rpcUrl)),
190
171
  signer,
191
- paymentUrl: options.paymentUrl,
192
- hyperbeamUrl: options.hyperbeamUrl,
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
- /** helper to get address from --address option first, then check wallet options */
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
- const signer = contractSignerFromOptions(options);
214
- if (signer !== undefined) {
215
- return signer.signerAddress;
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
  }
@@ -221,7 +248,7 @@ export function requiredAddressFromOptions(options) {
221
248
  if (address !== undefined) {
222
249
  return address;
223
250
  }
224
- throw new Error('No address provided. Use --address or --wallet-file');
251
+ throw new Error('No address provided. Use --address, --wallet-file, or --private-key');
225
252
  }
226
253
  const defaultCliPaginationLimit = 10; // more friendly UX than 100
227
254
  export function paginationParamsFromOptions(options) {
@@ -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, services, } = options;
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 ANTProcessFromOptions(options) {
448
- return new AOProcess({
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
- process: ANTProcessFromOptions(options),
459
- hyperbeamUrl: options.hyperbeamUrl,
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, signer) {
463
- signer ??= requiredContractSignerFromOptions(options).signer;
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
- process: ANTProcessFromOptions(options),
466
- signer,
467
- hyperbeamUrl: options.hyperbeamUrl,
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
- export function getANTStateFromOptions(options) {
516
- return initANTStateForAddress({
517
- owner: requiredAddressFromOptions(options),
518
- targetId: options.target,
519
- controllers: options.controllers,
520
- description: options.description,
521
- ticker: options.ticker,
522
- name: options.name,
523
- keywords: options.keywords,
524
- logo: options.logo,
525
- ttlSeconds: options.ttlSeconds !== undefined
526
- ? +options.ttlSeconds
527
- : defaultTtlSecondsCLI,
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,108 @@ export function fundFromFromOptions(o) {
557
583
  export function referrerFromOptions(o) {
558
584
  return o.referrer;
559
585
  }
586
+ /** Largest value representable by an unsigned 64-bit integer (2^64 - 1). */
587
+ const U64_MAX = (1n << 64n) - 1n;
588
+ /** Parse `--withdrawal-id` from CLI options into a bigint. */
589
+ export function withdrawalIdFromOptions(o) {
590
+ if (o.withdrawalId === undefined)
591
+ return undefined;
592
+ let id;
593
+ try {
594
+ id = BigInt(o.withdrawalId);
595
+ }
596
+ catch {
597
+ throw new Error(`Invalid --withdrawal-id: '${o.withdrawalId}' is not a valid u64 integer`);
598
+ }
599
+ // The on-chain seed encoder treats the id as a u64; reject out-of-range
600
+ // values here with a clear message instead of a downstream encoder error.
601
+ if (id < 0n || id > U64_MAX) {
602
+ throw new Error(`Invalid --withdrawal-id: '${o.withdrawalId}' is outside u64 range (0..${U64_MAX})`);
603
+ }
604
+ return id;
605
+ }
606
+ /**
607
+ * Parse `--funding-plan-json` into a `FundingSourceSpec[]`. Validates each
608
+ * entry's `kind` against the on-chain enum and parses `amount` as a bigint.
609
+ * Each Delegation/OperatorStake entry MAY carry a `gateway` field (base58
610
+ * Solana address) — required when the plan spans multiple gateways.
611
+ *
612
+ * Format examples:
613
+ * '[{"kind":"balance","amount":"100"},{"kind":"withdrawal","amount":"500"}]'
614
+ * '[{"kind":"delegation","amount":"100","gateway":"Gw1..."},{"kind":"delegation","amount":"50","gateway":"Gw2..."}]'
615
+ *
616
+ * Returns undefined when the flag isn't set; throws Error on malformed JSON
617
+ * or unknown kinds.
618
+ */
619
+ export function fundingPlanFromOptions(o) {
620
+ if (!o.fundingPlanJson)
621
+ return undefined;
622
+ let parsed;
623
+ try {
624
+ parsed = JSON.parse(o.fundingPlanJson);
625
+ }
626
+ catch (err) {
627
+ throw new Error(`--funding-plan-json is not valid JSON: ${err.message}`);
628
+ }
629
+ if (!Array.isArray(parsed)) {
630
+ throw new Error('--funding-plan-json must be a JSON array');
631
+ }
632
+ const validKinds = new Set([
633
+ 'balance',
634
+ 'delegation',
635
+ 'operatorStake',
636
+ 'withdrawal',
637
+ ]);
638
+ return parsed.map((entry, idx) => {
639
+ if (typeof entry !== 'object' ||
640
+ entry === null ||
641
+ typeof entry.kind !== 'string' ||
642
+ typeof entry.amount !== 'string') {
643
+ throw new Error(`--funding-plan-json[${idx}] must be { kind: string, amount: string, gateway?: string }`);
644
+ }
645
+ const e = entry;
646
+ if (!validKinds.has(e.kind)) {
647
+ throw new Error(`--funding-plan-json[${idx}].kind must be one of ${[...validKinds].join(', ')} (got '${e.kind}')`);
648
+ }
649
+ let amount;
650
+ try {
651
+ amount = BigInt(e.amount);
652
+ }
653
+ catch {
654
+ throw new Error(`--funding-plan-json[${idx}].amount '${e.amount}' is not a valid u64`);
655
+ }
656
+ if (amount <= 0n) {
657
+ throw new Error(`--funding-plan-json[${idx}].amount must be > 0 (got ${e.amount})`);
658
+ }
659
+ if (amount > U64_MAX) {
660
+ throw new Error(`--funding-plan-json[${idx}].amount '${e.amount}' exceeds u64 max (${U64_MAX})`);
661
+ }
662
+ const out = {
663
+ kind: e.kind,
664
+ amount,
665
+ };
666
+ if (e.gateway !== undefined) {
667
+ if (typeof e.gateway !== 'string') {
668
+ throw new Error(`--funding-plan-json[${idx}].gateway must be a base58 Solana address`);
669
+ }
670
+ if (e.kind !== 'delegation' && e.kind !== 'operatorStake') {
671
+ throw new Error(`--funding-plan-json[${idx}].gateway is only valid for kind 'delegation' or 'operatorStake' (got '${e.kind}')`);
672
+ }
673
+ // The gateway is going to be used: confirm it actually decodes as a
674
+ // base58 Solana address. `address()` throws on malformed input, so a
675
+ // bad string fails the CLI here rather than deep in instruction
676
+ // building.
677
+ try {
678
+ address(e.gateway);
679
+ }
680
+ catch {
681
+ throw new Error(`--funding-plan-json[${idx}].gateway '${e.gateway}' is not a valid base58 Solana address`);
682
+ }
683
+ out.gateway = e.gateway;
684
+ }
685
+ return out;
686
+ });
687
+ }
560
688
  export function assertLockLengthInRange(lockLengthMs, assertMin = true) {
561
689
  const minLockLengthMs = 1209600000; // 14 days
562
690
  const maxLockLengthMs = 378432000000; // ~12 years