@ar.io/sdk 4.0.0-solana.20 → 4.0.0-solana.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/common/ant.js +19 -14
- package/lib/esm/solana/ant-writeable.js +27 -10
- package/lib/esm/solana/io-writeable.js +153 -34
- package/lib/esm/solana/mpl-core.js +39 -0
- package/lib/esm/version.js +1 -1
- package/lib/types/common/ant.d.ts +7 -0
- package/lib/types/solana/io-writeable.d.ts +42 -4
- package/lib/types/solana/mpl-core.d.ts +29 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +2 -2
package/lib/esm/common/ant.js
CHANGED
|
@@ -3,18 +3,23 @@ export class ANT {
|
|
|
3
3
|
return (async () => {
|
|
4
4
|
const { SolanaANTReadable } = await import('../solana/ant-readable.js');
|
|
5
5
|
const { SolanaANTWriteable } = await import('../solana/ant-writeable.js');
|
|
6
|
-
// ADR-016 / BD-100: when no explicit `antProgramId` is passed,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
6
|
+
// ADR-016 / BD-100: when no explicit `antProgramId` is passed, resolve
|
|
7
|
+
// it from the asset's `ANT Program` Attributes-plugin entry so that
|
|
8
|
+
// third-party (BYO-ANT) assets derive PDAs through the right program.
|
|
9
|
+
//
|
|
10
|
+
// SECURITY: that trait is untrusted asset/RPC data. The *read* path may
|
|
11
|
+
// use it freely (it never signs). The *write* path runs it through
|
|
12
|
+
// `resolveWriteAntProgram`, which refuses to sign against a non-canonical
|
|
13
|
+
// detected value unless the caller opted in by passing `antProgramId`
|
|
14
|
+
// explicitly — otherwise a spoofed trait could route a signed tx to an
|
|
15
|
+
// attacker-controlled program.
|
|
16
|
+
const explicit = config.antProgramId;
|
|
17
|
+
const { fetchAntProgramFromAsset, resolveWriteAntProgram } = await import('../solana/mpl-core.js');
|
|
18
|
+
const { ARIO_ANT_PROGRAM_ID } = await import('../solana/constants.js');
|
|
19
|
+
const { address } = await import('@solana/kit');
|
|
20
|
+
const detected = explicit !== undefined
|
|
21
|
+
? null
|
|
22
|
+
: await fetchAntProgramFromAsset(config.rpc, address(config.processId), { commitment: config.commitment ?? 'confirmed' });
|
|
18
23
|
if (config.signer) {
|
|
19
24
|
if (!config.rpcSubscriptions) {
|
|
20
25
|
throw new Error('ANT.init({ signer }) requires rpcSubscriptions for transaction confirmation.');
|
|
@@ -25,14 +30,14 @@ export class ANT {
|
|
|
25
30
|
processId: config.processId,
|
|
26
31
|
signer: config.signer,
|
|
27
32
|
commitment: config.commitment,
|
|
28
|
-
antProgramId,
|
|
33
|
+
antProgramId: resolveWriteAntProgram({ explicit, detected }),
|
|
29
34
|
});
|
|
30
35
|
}
|
|
31
36
|
return new SolanaANTReadable({
|
|
32
37
|
rpc: config.rpc,
|
|
33
38
|
processId: config.processId,
|
|
34
39
|
commitment: config.commitment,
|
|
35
|
-
antProgramId,
|
|
40
|
+
antProgramId: explicit ?? detected ?? ARIO_ANT_PROGRAM_ID,
|
|
36
41
|
});
|
|
37
42
|
})();
|
|
38
43
|
}
|
|
@@ -264,21 +264,37 @@ export class SolanaANTWriteable extends SolanaANTReadable {
|
|
|
264
264
|
const newOwnerDest = await this.registry.resolveDestinationAclAccounts({
|
|
265
265
|
user: newOwner,
|
|
266
266
|
});
|
|
267
|
-
// Resolve the old owner's source ACL accounts.
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
// failing client-side here.
|
|
267
|
+
// Resolve the old owner's source ACL accounts. If the entry is
|
|
268
|
+
// missing (e.g. the ANT was acquired via a marketplace transfer or
|
|
269
|
+
// before the ACL system existed), heal it by bootstrapping the
|
|
270
|
+
// config/page and recording the owner entry so the on-chain
|
|
271
|
+
// transfer handler can successfully remove it.
|
|
273
272
|
const oldOwnerSource = await this.registry.resolveSourceAclAccountsForEntry({
|
|
274
273
|
user: oldOwner,
|
|
275
274
|
asset: this.mint,
|
|
276
275
|
role: 'owner',
|
|
277
276
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
277
|
+
let oldOwnerAclConfigPda;
|
|
278
|
+
let oldOwnerAclPagePda;
|
|
279
|
+
const oldOwnerHealIxs = [];
|
|
280
|
+
if (oldOwnerSource) {
|
|
281
|
+
oldOwnerAclConfigPda = oldOwnerSource.aclConfigPda;
|
|
282
|
+
oldOwnerAclPagePda = oldOwnerSource.aclPagePda;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const dest = await this.registry.resolveDestinationAclAccounts({
|
|
286
|
+
user: oldOwner,
|
|
287
|
+
});
|
|
288
|
+
oldOwnerAclConfigPda = dest.aclConfigPda;
|
|
289
|
+
oldOwnerAclPagePda = dest.aclPagePda;
|
|
290
|
+
oldOwnerHealIxs.push(...dest.prepIxs);
|
|
291
|
+
oldOwnerHealIxs.push(await this.registry.buildRecordIx({
|
|
292
|
+
user: oldOwner,
|
|
293
|
+
asset: this.mint,
|
|
294
|
+
role: 'owner',
|
|
295
|
+
pageIdx: dest.pageIdx,
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
282
298
|
const transferIx = await getTransferInstructionAsync({
|
|
283
299
|
asset: this.mint,
|
|
284
300
|
caller: this.signer,
|
|
@@ -306,6 +322,7 @@ export class SolanaANTWriteable extends SolanaANTReadable {
|
|
|
306
322
|
controllers: exControllers,
|
|
307
323
|
});
|
|
308
324
|
const sig = await this.sendTransaction([
|
|
325
|
+
...oldOwnerHealIxs,
|
|
309
326
|
...newOwnerDest.prepIxs,
|
|
310
327
|
transferIx,
|
|
311
328
|
...cleanupIxs,
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
* surface for them.
|
|
20
20
|
*/
|
|
21
21
|
import { AccountRole, address, fetchEncodedAccount, getAddressDecoder, } from '@solana/kit';
|
|
22
|
-
import { PurchaseType, getBuyNameFromDelegationInstructionAsync, getBuyNameFromFundingPlanInstructionAsync, getBuyNameFromOperatorStakeInstructionAsync, getBuyNameFromWithdrawalInstructionAsync, getBuyNameInstructionAsync, getBuyReturnedNameFromDelegationInstructionAsync, getBuyReturnedNameFromFundingPlanInstructionAsync, getBuyReturnedNameFromOperatorStakeInstructionAsync, getBuyReturnedNameFromWithdrawalInstructionAsync, getBuyReturnedNameInstructionAsync, getExtendLeaseFromDelegationInstructionAsync, getExtendLeaseFromFundingPlanInstructionAsync, getExtendLeaseFromOperatorStakeInstructionAsync, getExtendLeaseFromWithdrawalInstructionAsync, getExtendLeaseInstructionAsync, getIncreaseUndernameLimitFromDelegationInstructionAsync, getIncreaseUndernameLimitFromFundingPlanInstructionAsync, getIncreaseUndernameLimitFromOperatorStakeInstructionAsync, getIncreaseUndernameLimitFromWithdrawalInstructionAsync, getIncreaseUndernameLimitInstructionAsync, getPruneExpiredNamesInstructionAsync, getPruneExpiredReservationInstruction, getPruneNameToReturnedInstructionAsync, getPruneReturnedNamesInstructionAsync, getReassignNameInstructionAsync, getReleaseNameInstructionAsync, getUpgradeNameFromDelegationInstructionAsync, getUpgradeNameFromFundingPlanInstructionAsync, getUpgradeNameFromOperatorStakeInstructionAsync, getUpgradeNameFromWithdrawalInstructionAsync, getUpgradeNameInstructionAsync, } from '@ar.io/solana-contracts/arns';
|
|
22
|
+
import { PurchaseType, getBuyNameFromDelegationInstructionAsync, getBuyNameFromFundingPlanInstructionAsync, getBuyNameFromOperatorStakeInstructionAsync, getBuyNameFromWithdrawalInstructionAsync, getBuyNameInstructionAsync, getBuyReturnedNameFromDelegationInstructionAsync, getBuyReturnedNameFromFundingPlanInstructionAsync, getBuyReturnedNameFromOperatorStakeInstructionAsync, getBuyReturnedNameFromWithdrawalInstructionAsync, getBuyReturnedNameInstructionAsync, getExtendLeaseFromDelegationInstructionAsync, getExtendLeaseFromFundingPlanInstructionAsync, getExtendLeaseFromOperatorStakeInstructionAsync, getExtendLeaseFromWithdrawalInstructionAsync, getExtendLeaseInstructionAsync, getIncreaseUndernameLimitFromDelegationInstructionAsync, getIncreaseUndernameLimitFromFundingPlanInstructionAsync, getIncreaseUndernameLimitFromOperatorStakeInstructionAsync, getIncreaseUndernameLimitFromWithdrawalInstructionAsync, getIncreaseUndernameLimitInstructionAsync, getMigrateArnsRecordInstruction, getPruneExpiredNamesInstructionAsync, getPruneExpiredReservationInstruction, getPruneNameToReturnedInstructionAsync, getPruneReturnedNamesInstructionAsync, getReassignNameInstructionAsync, getReleaseNameInstructionAsync, getUpgradeNameFromDelegationInstructionAsync, getUpgradeNameFromFundingPlanInstructionAsync, getUpgradeNameFromOperatorStakeInstructionAsync, getUpgradeNameFromWithdrawalInstructionAsync, getUpgradeNameInstructionAsync, } from '@ar.io/solana-contracts/arns';
|
|
23
23
|
import { FundingSourceKind as GeneratedFundingSourceKindEnum } from '@ar.io/solana-contracts/gar';
|
|
24
24
|
import { buildCreateAtaIdempotentIx, getAssociatedTokenAddressKit, } from './ata.js';
|
|
25
|
-
import { deserializeArnsRecord, deserializeEpochSettingsFull, } from './deserialize.js';
|
|
25
|
+
import { deserializeArnsRecord, deserializeEpochSettingsFull, deserializePrimaryName, } from './deserialize.js';
|
|
26
26
|
import { buildFundingPlan as buildFundingPlanCore, buildFundingPlanRemainingAccounts, computeResidueIndexes, predictResidueVaults, } from './funding-plan.js';
|
|
27
27
|
/** Maps the SDK's user-facing FundingSourceKind string union to the
|
|
28
28
|
* Codama-generated enum used by the on-chain ix payload. */
|
|
@@ -36,11 +36,11 @@ function toGeneratedFundingSourceSpec(s) {
|
|
|
36
36
|
return { kind: kindMap[s.kind], amount: s.amount };
|
|
37
37
|
}
|
|
38
38
|
import { getSyncAttributesInstruction } from '@ar.io/solana-contracts/ant';
|
|
39
|
-
import { getApprovePrimaryNameInstructionAsync, getCloseExpiredRequestInstruction, getCreateVaultInstructionAsync, getExtendVaultInstructionAsync, getIncreaseVaultInstructionAsync, getReleaseVaultInstructionAsync, getRequestAndSetPrimaryNameFromFundingPlanInstructionAsync, getRequestAndSetPrimaryNameInstructionAsync, getRequestPrimaryNameFromFundingPlanInstructionAsync, getRequestPrimaryNameInstructionAsync, getRevokeVaultInstructionAsync, getVaultedTransferInstructionAsync, } from '@ar.io/solana-contracts/core';
|
|
39
|
+
import { getApprovePrimaryNameInstructionAsync, getCloseExpiredRequestInstruction, getCreateVaultInstructionAsync, getExtendVaultInstructionAsync, getIncreaseVaultInstructionAsync, getReleaseVaultInstructionAsync, getRemovePrimaryNameInstructionAsync, getRequestAndSetPrimaryNameFromFundingPlanInstructionAsync, getRequestAndSetPrimaryNameInstructionAsync, getRequestPrimaryNameFromFundingPlanInstructionAsync, getRequestPrimaryNameInstructionAsync, getRevokeVaultInstructionAsync, getVaultedTransferInstructionAsync, } from '@ar.io/solana-contracts/core';
|
|
40
40
|
import { getDelegationDecoder, getGatewayDecoder, } from '@ar.io/solana-contracts/gar';
|
|
41
41
|
import { Protocol, getAllowDelegateInstructionAsync, getCancelWithdrawalInstruction, getClaimDelegateFromLeavingGatewayInstructionAsync, getClaimWithdrawalInstructionAsync, getCloseDrainedWithdrawalInstruction, getCloseEmptyDelegationInstruction, getCloseEpochInstructionAsync, getCloseObservationInstructionAsync, getCreateEpochInstructionAsync, getDecreaseDelegateStakeInstructionAsync, getDecreaseOperatorStakeInstructionAsync, getDelegateStakeInstructionAsync, getDisallowDelegateInstructionAsync, getDistributeEpochInstructionAsync, getFinalizeGoneInstructionAsync, getIncreaseOperatorStakeInstructionAsync, getInstantWithdrawalInstructionAsync, getJoinNetworkInstructionAsync, getLeaveNetworkInstructionAsync, getPrescribeEpochInstructionAsync, getPruneGatewayInstructionAsync, getRedelegateStakeInstructionAsync, getSaveObservationsInstructionAsync, getSetAllowlistEnabledInstructionAsync, getTallyWeightsInstructionAsync, getUpdateGatewaySettingsInstructionAsync, } from '@ar.io/solana-contracts/gar';
|
|
42
42
|
import { getTransferCheckedInstruction } from '@solana-program/token';
|
|
43
|
-
import { TOKEN_DECIMALS } from './constants.js';
|
|
43
|
+
import { ARIO_ANT_PROGRAM_ID, TOKEN_DECIMALS } from './constants.js';
|
|
44
44
|
import { SolanaARIOReadable } from './io-readable.js';
|
|
45
45
|
import { getAntRecordPDA, getArioConfigPDA, getArnsRecordPDA, getArnsRegistryPDA, getArnsSettingsPDA, getDelegationPDA, getDemandFactorPDA, getEpochPDA, getEpochSettingsPDA, getGarSettingsPDA, getGatewayPDA, getGatewayRegistryPDA, getObservationPDA, getObserverLookupPDA, getPrimaryNamePDA, getPrimaryNameRequestPDA, getPrimaryNameReversePDA, getReservedNamePDA, getReturnedNamePDA, getVaultPDA, getWithdrawalCounterPDA, getWithdrawalPDA, hashName, } from './pda.js';
|
|
46
46
|
import { sendAndConfirm } from './send.js';
|
|
@@ -280,6 +280,35 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
280
280
|
const [nameRegistry] = await getArnsRegistryPDA(this.arnsProgram);
|
|
281
281
|
return { config, demandFactor, nameRegistry, ...input };
|
|
282
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* If the on-chain ArnsRecord for `name` hasn't been migrated to the
|
|
285
|
+
* current schema (name_hash at offset 8 doesn't match the expected
|
|
286
|
+
* hash), return a `migrate_arns_record` instruction that must be
|
|
287
|
+
* prepended to any operation referencing the record with PDA seed
|
|
288
|
+
* verification.
|
|
289
|
+
*
|
|
290
|
+
* Returns an empty array when the record is already up-to-date or
|
|
291
|
+
* doesn't exist.
|
|
292
|
+
*/
|
|
293
|
+
async _buildMigrateArnsRecordIxIfNeeded(name) {
|
|
294
|
+
const [arnsRecordPda] = await getArnsRecordPDA(name, this.arnsProgram);
|
|
295
|
+
const account = await fetchEncodedAccount(this.rpc, arnsRecordPda, {
|
|
296
|
+
commitment: this.commitment,
|
|
297
|
+
});
|
|
298
|
+
if (!account.exists)
|
|
299
|
+
return [];
|
|
300
|
+
const data = Buffer.from(account.data);
|
|
301
|
+
const expectedHash = hashName(name);
|
|
302
|
+
const storedHash = data.subarray(8, 40);
|
|
303
|
+
if (storedHash.equals(expectedHash))
|
|
304
|
+
return [];
|
|
305
|
+
return [
|
|
306
|
+
getMigrateArnsRecordInstruction({
|
|
307
|
+
record: arnsRecordPda,
|
|
308
|
+
payer: this.signer,
|
|
309
|
+
}, { programAddress: this.arnsProgram }),
|
|
310
|
+
];
|
|
311
|
+
}
|
|
283
312
|
/** Inject ARIO core default PDAs (config). */
|
|
284
313
|
async withCoreDefaults(input) {
|
|
285
314
|
const [config] = await getArioConfigPDA(this.coreProgram);
|
|
@@ -1046,12 +1075,13 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1046
1075
|
return pda;
|
|
1047
1076
|
}
|
|
1048
1077
|
async upgradeRecord(params, _options) {
|
|
1078
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1049
1079
|
const ix = await this._buildManageStakeIx({
|
|
1050
1080
|
params,
|
|
1051
1081
|
operation: 'upgrade',
|
|
1052
1082
|
});
|
|
1053
1083
|
const syncIx = await this._buildSyncAttributesIxIfOwner(params.name);
|
|
1054
|
-
const sig = await this.sendTransaction(syncIx ? [ix, syncIx] : [ix]);
|
|
1084
|
+
const sig = await this.sendTransaction(syncIx ? [...migrateIxs, ix, syncIx] : [...migrateIxs, ix]);
|
|
1055
1085
|
return { id: sig };
|
|
1056
1086
|
}
|
|
1057
1087
|
async syncAttributes(params, _options) {
|
|
@@ -1061,8 +1091,9 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1061
1091
|
// `_buildSyncAttributesIxIfOwner`, which skips when not the owner so
|
|
1062
1092
|
// the wrapping arns ix can still succeed for non-holder management
|
|
1063
1093
|
// — see BD-095.)
|
|
1094
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1064
1095
|
const ix = await this._buildSyncAttributesIxUnconditional(params.name);
|
|
1065
|
-
const sig = await this.sendTransaction([ix]);
|
|
1096
|
+
const sig = await this.sendTransaction([...migrateIxs, ix]);
|
|
1066
1097
|
return { id: sig };
|
|
1067
1098
|
}
|
|
1068
1099
|
/**
|
|
@@ -1130,6 +1161,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1130
1161
|
}, { programAddress: this.antProgram });
|
|
1131
1162
|
}
|
|
1132
1163
|
async extendLease(params, _options) {
|
|
1164
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1133
1165
|
const ix = await this._buildManageStakeIx({
|
|
1134
1166
|
params,
|
|
1135
1167
|
operation: 'extend',
|
|
@@ -1137,17 +1169,18 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1137
1169
|
});
|
|
1138
1170
|
// BD-095: extend_lease changes only `end_timestamp`, which isn't
|
|
1139
1171
|
// mirrored in any Metaplex Attributes plugin trait. No bundle.
|
|
1140
|
-
const sig = await this.sendTransaction([ix]);
|
|
1172
|
+
const sig = await this.sendTransaction([...migrateIxs, ix]);
|
|
1141
1173
|
return { id: sig };
|
|
1142
1174
|
}
|
|
1143
1175
|
async increaseUndernameLimit(params, _options) {
|
|
1176
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1144
1177
|
const ix = await this._buildManageStakeIx({
|
|
1145
1178
|
params,
|
|
1146
1179
|
operation: 'increaseUndername',
|
|
1147
1180
|
quantity: params.increaseCount,
|
|
1148
1181
|
});
|
|
1149
1182
|
const syncIx = await this._buildSyncAttributesIxIfOwner(params.name);
|
|
1150
|
-
const sig = await this.sendTransaction(syncIx ? [ix, syncIx] : [ix]);
|
|
1183
|
+
const sig = await this.sendTransaction(syncIx ? [...migrateIxs, ix, syncIx] : [...migrateIxs, ix]);
|
|
1151
1184
|
return { id: sig };
|
|
1152
1185
|
}
|
|
1153
1186
|
/**
|
|
@@ -1312,6 +1345,60 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1312
1345
|
// =========================================
|
|
1313
1346
|
// Primary name operations (ario-core)
|
|
1314
1347
|
// =========================================
|
|
1348
|
+
/**
|
|
1349
|
+
* If the signer already has a primary name set, build the instruction(s)
|
|
1350
|
+
* needed to remove it so they can be prepended to a request/set tx —
|
|
1351
|
+
* enabling single-tx "change primary name" flows.
|
|
1352
|
+
*
|
|
1353
|
+
* Returns an empty array when the signer has no existing primary name.
|
|
1354
|
+
*
|
|
1355
|
+
* Throws when the signer has a legacy primary-name state (forward
|
|
1356
|
+
* `PrimaryName` PDA exists but its paired `PrimaryNameReverse` PDA does
|
|
1357
|
+
* NOT). Both `remove_primary_name` AND `request_and_set_primary_name`
|
|
1358
|
+
* require the reverse PDA on-chain — the latter rejects with
|
|
1359
|
+
* `MustRemoveExistingPrimaryName` (0x1786, code 6022) any time a
|
|
1360
|
+
* forward record already exists for the signer, regardless of reverse
|
|
1361
|
+
* state. Silently skipping the remove would queue a tx guaranteed to
|
|
1362
|
+
* fail with that opaque error. Surfacing it at the client with a clear
|
|
1363
|
+
* remediation pointer is the only safe behavior.
|
|
1364
|
+
*
|
|
1365
|
+
* The legacy state should not exist on any cluster post-snapshot/import
|
|
1366
|
+
* PR #159 (which emits PrimaryNameReverse in lockstep with PrimaryName)
|
|
1367
|
+
* — it's a relic of pre-#159 imports. Operators on affected clusters
|
|
1368
|
+
* must run `yarn workspace @ar-io/migration-import backfill:primary-name-reverse`
|
|
1369
|
+
* (in the solana-ar-io repo) before this method can succeed.
|
|
1370
|
+
*/
|
|
1371
|
+
async _buildRemoveExistingPrimaryNameIxs() {
|
|
1372
|
+
const [primaryNamePda] = await getPrimaryNamePDA(this.signer.address, this.coreProgram);
|
|
1373
|
+
const account = await fetchEncodedAccount(this.rpc, primaryNamePda, {
|
|
1374
|
+
commitment: this.commitment,
|
|
1375
|
+
});
|
|
1376
|
+
if (!account.exists)
|
|
1377
|
+
return [];
|
|
1378
|
+
const { name: oldName } = deserializePrimaryName(Buffer.from(account.data));
|
|
1379
|
+
const [primaryNameReversePda] = await getPrimaryNameReversePDA(oldName, this.coreProgram);
|
|
1380
|
+
const reverseAccount = await fetchEncodedAccount(this.rpc, primaryNameReversePda, { commitment: this.commitment });
|
|
1381
|
+
if (!reverseAccount.exists) {
|
|
1382
|
+
// Fail fast with an actionable message. See method docstring for
|
|
1383
|
+
// why request_and_set would reject this regardless.
|
|
1384
|
+
throw new Error(`Cannot change primary name: signer "${this.signer.address}" has a ` +
|
|
1385
|
+
`legacy PrimaryName ("${oldName}") with no paired PrimaryNameReverse PDA ` +
|
|
1386
|
+
`(${primaryNameReversePda}). The on-chain remove_primary_name and ` +
|
|
1387
|
+
`request_and_set_primary_name ixs both require the reverse PDA — ` +
|
|
1388
|
+
`request_and_set will reject with MustRemoveExistingPrimaryName (code 6022). ` +
|
|
1389
|
+
`Run \`yarn workspace @ar-io/migration-import backfill:primary-name-reverse\` ` +
|
|
1390
|
+
`against this cluster's ario-core program to materialize the missing reverse ` +
|
|
1391
|
+
`PDA, then retry.`);
|
|
1392
|
+
}
|
|
1393
|
+
return [
|
|
1394
|
+
await getRemovePrimaryNameInstructionAsync({
|
|
1395
|
+
primaryName: primaryNamePda,
|
|
1396
|
+
primaryNameReverse: primaryNameReversePda,
|
|
1397
|
+
owner: this.signer,
|
|
1398
|
+
reverseLookupHash: hashName(oldName),
|
|
1399
|
+
}, { programAddress: this.coreProgram }),
|
|
1400
|
+
];
|
|
1401
|
+
}
|
|
1315
1402
|
/**
|
|
1316
1403
|
* Build the `remaining_accounts` slice + the `antProgramId` arg the
|
|
1317
1404
|
* four ario-core primary-name instructions consume. Sprint 2/5
|
|
@@ -1333,10 +1420,13 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1333
1420
|
*
|
|
1334
1421
|
* `antProgram` honors ADR-016 / BD-100 pluggability: the asset's
|
|
1335
1422
|
* `ANT Program` Attributes-plugin trait selects which program owns the
|
|
1336
|
-
* AntRecord PDA. Absent / unparseable → canonical fallback.
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1423
|
+
* AntRecord PDA. Absent / unparseable → canonical fallback. The detected
|
|
1424
|
+
* trait is untrusted asset/RPC data, so it is honored only when it matches
|
|
1425
|
+
* the canonical program or this client's explicitly-configured
|
|
1426
|
+
* `this.antProgram`; any other value falls back to the configured program
|
|
1427
|
+
* (see the SECURITY note in the body). Both the PDA derivation here and the
|
|
1428
|
+
* `ant_program_id` arg the caller passes to the on-chain ix MUST agree (the
|
|
1429
|
+
* handler re-derives and rejects mismatches).
|
|
1340
1430
|
*/
|
|
1341
1431
|
async _buildPrimaryNameValidationAccounts(name, variant) {
|
|
1342
1432
|
const { isUndername, baseName, undername } = splitPrimaryName(name);
|
|
@@ -1364,10 +1454,26 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1364
1454
|
const arnsRecord = deserializeArnsRecord(Buffer.from(arnsAccount.data));
|
|
1365
1455
|
const antMint = address(arnsRecord.processId);
|
|
1366
1456
|
const { fetchAntProgramFromAsset } = await import('./mpl-core.js');
|
|
1457
|
+
const detected = await fetchAntProgramFromAsset(this.rpc, antMint, {
|
|
1458
|
+
commitment: this.commitment,
|
|
1459
|
+
});
|
|
1460
|
+
// SECURITY (BD-100 / SDK ANT-program auth finding): the asset's
|
|
1461
|
+
// `ANT Program` trait is untrusted asset/RPC data. Only honor a detected
|
|
1462
|
+
// value when it matches the canonical program or the program this client
|
|
1463
|
+
// was explicitly configured with (`this.antProgram`); otherwise fall back
|
|
1464
|
+
// to the configured program so a spoofed trait can't redirect the
|
|
1465
|
+
// AntRecord PDA derivation. This path only derives a READONLY validation
|
|
1466
|
+
// account (the instruction itself targets the canonical core program), so
|
|
1467
|
+
// the fallback is silent rather than a throw — but the gate keeps an
|
|
1468
|
+
// attacker from steering PDA derivation. Heterogeneous BYO-ANT primary
|
|
1469
|
+
// names (an asset on a non-configured program) await the contract-side
|
|
1470
|
+
// resolution of the `ant_program == ario_ant::ID` pin; see the
|
|
1471
|
+
// accompanying security note.
|
|
1367
1472
|
antProgram =
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1473
|
+
detected !== null &&
|
|
1474
|
+
(detected === ARIO_ANT_PROGRAM_ID || detected === this.antProgram)
|
|
1475
|
+
? detected
|
|
1476
|
+
: this.antProgram;
|
|
1371
1477
|
// removeForBaseName always uses the "@" undername (the base-name
|
|
1372
1478
|
// owner's record). The other ANT-auth variants use the undername
|
|
1373
1479
|
// part if the primary name is an undername.
|
|
@@ -1380,6 +1486,11 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1380
1486
|
return { remaining, antProgram };
|
|
1381
1487
|
}
|
|
1382
1488
|
async requestPrimaryName(params, _options) {
|
|
1489
|
+
// If the caller already has a primary name, prepend remove ixs so
|
|
1490
|
+
// the on-chain handler doesn't reject with MustRemoveExistingPrimaryName.
|
|
1491
|
+
const removeIxs = await this._buildRemoveExistingPrimaryNameIxs();
|
|
1492
|
+
const { baseName } = splitPrimaryName(params.name);
|
|
1493
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(baseName);
|
|
1383
1494
|
const coreConfig = await this.getCoreConfig();
|
|
1384
1495
|
const signerATA = await getAssociatedTokenAddressKit(coreConfig.mint, this.signer.address);
|
|
1385
1496
|
const { remaining } = await this._buildPrimaryNameValidationAccounts(params.name, 'request');
|
|
@@ -1405,13 +1516,18 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1405
1516
|
operation: 'request',
|
|
1406
1517
|
});
|
|
1407
1518
|
}
|
|
1408
|
-
const sig = await this.sendTransaction([ix]);
|
|
1519
|
+
const sig = await this.sendTransaction([...removeIxs, ...migrateIxs, ix]);
|
|
1409
1520
|
return { id: sig };
|
|
1410
1521
|
}
|
|
1411
1522
|
async setPrimaryName(params, _options) {
|
|
1412
1523
|
// setPrimaryName routes to the on-chain `request_and_set_primary_name`
|
|
1413
1524
|
// path — the auto-approve flow when the caller owns the AntRecord
|
|
1414
1525
|
// for the matching name (undername part, or "@" for base names).
|
|
1526
|
+
// If the caller already has a primary name, prepend remove ixs so
|
|
1527
|
+
// the "change" is atomic in a single transaction.
|
|
1528
|
+
const removeIxs = await this._buildRemoveExistingPrimaryNameIxs();
|
|
1529
|
+
const { baseName } = splitPrimaryName(params.name);
|
|
1530
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(baseName);
|
|
1415
1531
|
const coreConfig = await this.getCoreConfig();
|
|
1416
1532
|
const signerATA = await getAssociatedTokenAddressKit(coreConfig.mint, this.signer.address);
|
|
1417
1533
|
const { remaining, antProgram } = await this._buildPrimaryNameValidationAccounts(params.name, 'requestAndSet');
|
|
@@ -1437,7 +1553,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1437
1553
|
antProgramId: antProgram,
|
|
1438
1554
|
});
|
|
1439
1555
|
}
|
|
1440
|
-
const sig = await this.sendTransaction([ix]);
|
|
1556
|
+
const sig = await this.sendTransaction([...removeIxs, ...migrateIxs, ix]);
|
|
1441
1557
|
return { id: sig };
|
|
1442
1558
|
}
|
|
1443
1559
|
/**
|
|
@@ -1525,6 +1641,8 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1525
1641
|
* remaining_accounts: [arns_record(base), ant_record(undername | @)].
|
|
1526
1642
|
*/
|
|
1527
1643
|
async approvePrimaryName(params, _options) {
|
|
1644
|
+
const { baseName } = splitPrimaryName(params.name);
|
|
1645
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(baseName);
|
|
1528
1646
|
const [requestPda] = await getPrimaryNameRequestPDA(params.initiator, this.coreProgram);
|
|
1529
1647
|
const [primaryNamePda] = await getPrimaryNamePDA(params.initiator, this.coreProgram);
|
|
1530
1648
|
const [primaryNameReversePda] = await getPrimaryNameReversePDA(params.name, this.coreProgram);
|
|
@@ -1546,7 +1664,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1546
1664
|
reverseLookupHash: hashName(params.name),
|
|
1547
1665
|
antProgramId: antProgram,
|
|
1548
1666
|
}), { programAddress: this.coreProgram }), remaining);
|
|
1549
|
-
const sig = await this.sendTransaction([ix]);
|
|
1667
|
+
const sig = await this.sendTransaction([...migrateIxs, ix]);
|
|
1550
1668
|
return { id: sig };
|
|
1551
1669
|
}
|
|
1552
1670
|
// =========================================
|
|
@@ -1784,18 +1902,14 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1784
1902
|
// =========================================
|
|
1785
1903
|
/** Reassign an ArNS name to a different ANT. */
|
|
1786
1904
|
async reassignName(params, _options) {
|
|
1905
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1787
1906
|
const newAnt = address(params.processId);
|
|
1788
1907
|
const [arnsRecord] = await getArnsRecordPDA(params.name, this.arnsProgram);
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
// `ant_asset` account constrained to `arns_record.ant`. We must read the
|
|
1792
|
-
// current record to know which asset to pass — that's the OLD ant (the
|
|
1793
|
-
// one we're reassigning AWAY FROM), not `newAnt`.
|
|
1794
|
-
const currentRecord = await this.getArNSRecord({ name: params.name });
|
|
1795
|
-
const currentAnt = address(currentRecord.processId);
|
|
1908
|
+
const record = await this.getArNSRecord({ name: params.name });
|
|
1909
|
+
const antAsset = address(record.processId);
|
|
1796
1910
|
const ix = await getReassignNameInstructionAsync(await this.withArnsDefaults({
|
|
1797
1911
|
arnsRecord,
|
|
1798
|
-
antAsset
|
|
1912
|
+
antAsset,
|
|
1799
1913
|
caller: this.signer,
|
|
1800
1914
|
newAnt,
|
|
1801
1915
|
}), { programAddress: this.arnsProgram });
|
|
@@ -1809,21 +1923,25 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1809
1923
|
// the new ANT's holder; otherwise the ix is sent alone and the new
|
|
1810
1924
|
// owner runs `syncAttributes()` later (BD-095/096).
|
|
1811
1925
|
const syncIx = await this._buildSyncAttributesIxIfOwner(params.name, newAnt);
|
|
1812
|
-
const
|
|
1926
|
+
const reassignWithMetas = withRemainingAccounts(ix, [
|
|
1927
|
+
{ address: newAnt, role: AccountRole.READONLY },
|
|
1928
|
+
]);
|
|
1929
|
+
const sig = await this.sendTransaction(syncIx
|
|
1930
|
+
? [...migrateIxs, reassignWithMetas, syncIx]
|
|
1931
|
+
: [...migrateIxs, reassignWithMetas]);
|
|
1813
1932
|
return { id: sig };
|
|
1814
1933
|
}
|
|
1815
1934
|
/** Release a permabuy name back to the registry (creates a returned name auction). */
|
|
1816
1935
|
async releaseName(params, _options) {
|
|
1936
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
1817
1937
|
const [returnedNamePda] = await getReturnedNamePDA(params.name, this.arnsProgram);
|
|
1818
1938
|
const [arnsRecord] = await getArnsRecordPDA(params.name, this.arnsProgram);
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
// constrained to `arns_record.ant`. Fetch the record to know which.
|
|
1822
|
-
const currentRecord = await this.getArNSRecord({ name: params.name });
|
|
1939
|
+
const record = await this.getArNSRecord({ name: params.name });
|
|
1940
|
+
const antAsset = address(record.processId);
|
|
1823
1941
|
const ix = await getReleaseNameInstructionAsync(await this.withArnsDefaults({
|
|
1824
1942
|
arnsRecord,
|
|
1825
1943
|
returnedName: returnedNamePda,
|
|
1826
|
-
antAsset
|
|
1944
|
+
antAsset,
|
|
1827
1945
|
caller: this.signer,
|
|
1828
1946
|
}), { programAddress: this.arnsProgram });
|
|
1829
1947
|
// Note: no sync_attributes bundle here — release_name closes the
|
|
@@ -1831,7 +1949,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
1831
1949
|
// asset's stale traits remain pointing at the released name; off-chain
|
|
1832
1950
|
// resolvers should treat ArnsRecord as the source of truth and ignore
|
|
1833
1951
|
// a "ArNS Name" trait that no longer resolves.
|
|
1834
|
-
const sig = await this.sendTransaction([ix]);
|
|
1952
|
+
const sig = await this.sendTransaction([...migrateIxs, ix]);
|
|
1835
1953
|
return { id: sig };
|
|
1836
1954
|
}
|
|
1837
1955
|
// =========================================
|
|
@@ -2081,6 +2199,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2081
2199
|
* (kicks off the Dutch auction). Permissionless.
|
|
2082
2200
|
*/
|
|
2083
2201
|
async pruneNameToReturned(params, _options) {
|
|
2202
|
+
const migrateIxs = await this._buildMigrateArnsRecordIxIfNeeded(params.name);
|
|
2084
2203
|
const [arnsRecord] = await getArnsRecordPDA(params.name, this.arnsProgram);
|
|
2085
2204
|
const [returnedName] = await getReturnedNamePDA(params.name, this.arnsProgram);
|
|
2086
2205
|
const ix = await getPruneNameToReturnedInstructionAsync(await this.withArnsDefaults({
|
|
@@ -2088,7 +2207,7 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2088
2207
|
returnedName,
|
|
2089
2208
|
payer: this.signer,
|
|
2090
2209
|
}), { programAddress: this.arnsProgram });
|
|
2091
|
-
const sig = await this.sendTransaction([ix]);
|
|
2210
|
+
const sig = await this.sendTransaction([...migrateIxs, ix]);
|
|
2092
2211
|
return { id: sig };
|
|
2093
2212
|
}
|
|
2094
2213
|
/**
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* `ANT Program` attribute or has a layout the walker doesn't recognise.
|
|
27
27
|
*/
|
|
28
28
|
import { fetchEncodedAccount, } from '@solana/kit';
|
|
29
|
+
import { ARIO_ANT_PROGRAM_ID } from './constants.js';
|
|
29
30
|
import { getAssetV1Decoder } from './generated/mpl-core/accounts/assetV1.js';
|
|
30
31
|
import { getPluginHeaderV1Decoder } from './generated/mpl-core/accounts/pluginHeaderV1.js';
|
|
31
32
|
import { getPluginRegistryV1Decoder } from './generated/mpl-core/accounts/pluginRegistryV1.js';
|
|
@@ -116,6 +117,44 @@ export async function fetchAntProgramFromAsset(rpc, mint, config) {
|
|
|
116
117
|
// self-grief that surfaces at the next PDA derivation.
|
|
117
118
|
return value;
|
|
118
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Decide which ANT program id to trust for a SIGNING (write) transaction.
|
|
122
|
+
*
|
|
123
|
+
* The `ANT Program` trait returned by {@link fetchAntProgramFromAsset} is read
|
|
124
|
+
* from Metaplex Core asset / RPC data and is NOT authenticated — a malicious
|
|
125
|
+
* asset owner, or a spoofed/compromised RPC response, can set it to an
|
|
126
|
+
* attacker-controlled program. Write instructions use this value as the
|
|
127
|
+
* instruction `programAddress` while including the caller as a signer, so
|
|
128
|
+
* silently trusting a non-canonical detected value would let an attacker route
|
|
129
|
+
* a victim's signed transaction to their own program and inherit signer /
|
|
130
|
+
* writable privileges over the included accounts.
|
|
131
|
+
*
|
|
132
|
+
* Trust rules — BYO-ANT (ADR-016 / BD-100) stays supported, but only via
|
|
133
|
+
* explicit opt-in:
|
|
134
|
+
* - An `explicit` program id (the caller passed `antProgramId`) is always
|
|
135
|
+
* honored — the caller has taken responsibility for it.
|
|
136
|
+
* - Otherwise a detected value is honored only when it is `null` (no trait)
|
|
137
|
+
* or equals the canonical {@link ARIO_ANT_PROGRAM_ID}.
|
|
138
|
+
* - A detected NON-canonical value with no explicit opt-in throws, rather
|
|
139
|
+
* than silently signing against an untrusted program.
|
|
140
|
+
*
|
|
141
|
+
* Read-only paths never sign, so they may auto-detect freely; this guard is
|
|
142
|
+
* only for the write path.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveWriteAntProgram(args) {
|
|
145
|
+
const canonical = args.canonical ?? ARIO_ANT_PROGRAM_ID;
|
|
146
|
+
if (args.explicit !== undefined)
|
|
147
|
+
return args.explicit;
|
|
148
|
+
if (args.detected === null || args.detected === canonical)
|
|
149
|
+
return canonical;
|
|
150
|
+
throw new Error(`ANT.init auto-detected a non-canonical ANT program (${args.detected}) ` +
|
|
151
|
+
`from the asset's "${TRAIT_KEY_ANT_PROGRAM}" trait. That value is read ` +
|
|
152
|
+
`from asset/RPC data and is not authenticated, so the SDK refuses to ` +
|
|
153
|
+
`build a *signed* transaction against it implicitly (doing so could ` +
|
|
154
|
+
`route your signature to an attacker-controlled program). To use a ` +
|
|
155
|
+
`third-party (BYO-ANT) program for writes, pass it explicitly: ` +
|
|
156
|
+
`ANT.init({ ..., antProgramId }).`);
|
|
157
|
+
}
|
|
119
158
|
/**
|
|
120
159
|
* Fetch the current owner of an MPL Core asset. Returns `null` when the
|
|
121
160
|
* account doesn't exist or the AssetV1 layout can't be decoded.
|
package/lib/esm/version.js
CHANGED
|
@@ -27,6 +27,13 @@ import type { ANTRead, ANTWrite } from '../types/ant.js';
|
|
|
27
27
|
* (BYO-ANT) and falls back to the canonical default. Callers who already know
|
|
28
28
|
* the program (e.g. immediately after `ANT.spawn`) can pass it explicitly to
|
|
29
29
|
* skip the lookup.
|
|
30
|
+
*
|
|
31
|
+
* SECURITY: the `ANT Program` trait is untrusted asset/RPC data. For a
|
|
32
|
+
* read-only client it's used as-is, but a writeable client (signer present)
|
|
33
|
+
* will NOT sign against an auto-detected *non-canonical* program — it throws
|
|
34
|
+
* unless you opted in by passing `antProgramId` explicitly. This prevents a
|
|
35
|
+
* malicious asset or spoofed RPC response from redirecting your signed
|
|
36
|
+
* transaction to an attacker-controlled program.
|
|
30
37
|
*/
|
|
31
38
|
export type ANTConfig = {
|
|
32
39
|
processId: string;
|
|
@@ -109,6 +109,17 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
109
109
|
* (codama only reads the named keys from `input`).
|
|
110
110
|
*/
|
|
111
111
|
private withArnsDefaults;
|
|
112
|
+
/**
|
|
113
|
+
* If the on-chain ArnsRecord for `name` hasn't been migrated to the
|
|
114
|
+
* current schema (name_hash at offset 8 doesn't match the expected
|
|
115
|
+
* hash), return a `migrate_arns_record` instruction that must be
|
|
116
|
+
* prepended to any operation referencing the record with PDA seed
|
|
117
|
+
* verification.
|
|
118
|
+
*
|
|
119
|
+
* Returns an empty array when the record is already up-to-date or
|
|
120
|
+
* doesn't exist.
|
|
121
|
+
*/
|
|
122
|
+
private _buildMigrateArnsRecordIxIfNeeded;
|
|
112
123
|
/** Inject ARIO core default PDAs (config). */
|
|
113
124
|
private withCoreDefaults;
|
|
114
125
|
/** Inject GAR default PDAs (settings, epochSettings, registry). */
|
|
@@ -266,6 +277,30 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
266
277
|
* Reads the live DemandFactor account + applies the on-chain pricing math.
|
|
267
278
|
*/
|
|
268
279
|
private _estimateManageStakeCost;
|
|
280
|
+
/**
|
|
281
|
+
* If the signer already has a primary name set, build the instruction(s)
|
|
282
|
+
* needed to remove it so they can be prepended to a request/set tx —
|
|
283
|
+
* enabling single-tx "change primary name" flows.
|
|
284
|
+
*
|
|
285
|
+
* Returns an empty array when the signer has no existing primary name.
|
|
286
|
+
*
|
|
287
|
+
* Throws when the signer has a legacy primary-name state (forward
|
|
288
|
+
* `PrimaryName` PDA exists but its paired `PrimaryNameReverse` PDA does
|
|
289
|
+
* NOT). Both `remove_primary_name` AND `request_and_set_primary_name`
|
|
290
|
+
* require the reverse PDA on-chain — the latter rejects with
|
|
291
|
+
* `MustRemoveExistingPrimaryName` (0x1786, code 6022) any time a
|
|
292
|
+
* forward record already exists for the signer, regardless of reverse
|
|
293
|
+
* state. Silently skipping the remove would queue a tx guaranteed to
|
|
294
|
+
* fail with that opaque error. Surfacing it at the client with a clear
|
|
295
|
+
* remediation pointer is the only safe behavior.
|
|
296
|
+
*
|
|
297
|
+
* The legacy state should not exist on any cluster post-snapshot/import
|
|
298
|
+
* PR #159 (which emits PrimaryNameReverse in lockstep with PrimaryName)
|
|
299
|
+
* — it's a relic of pre-#159 imports. Operators on affected clusters
|
|
300
|
+
* must run `yarn workspace @ar-io/migration-import backfill:primary-name-reverse`
|
|
301
|
+
* (in the solana-ar-io repo) before this method can succeed.
|
|
302
|
+
*/
|
|
303
|
+
private _buildRemoveExistingPrimaryNameIxs;
|
|
269
304
|
/**
|
|
270
305
|
* Build the `remaining_accounts` slice + the `antProgramId` arg the
|
|
271
306
|
* four ario-core primary-name instructions consume. Sprint 2/5
|
|
@@ -287,10 +322,13 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
287
322
|
*
|
|
288
323
|
* `antProgram` honors ADR-016 / BD-100 pluggability: the asset's
|
|
289
324
|
* `ANT Program` Attributes-plugin trait selects which program owns the
|
|
290
|
-
* AntRecord PDA. Absent / unparseable → canonical fallback.
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
325
|
+
* AntRecord PDA. Absent / unparseable → canonical fallback. The detected
|
|
326
|
+
* trait is untrusted asset/RPC data, so it is honored only when it matches
|
|
327
|
+
* the canonical program or this client's explicitly-configured
|
|
328
|
+
* `this.antProgram`; any other value falls back to the configured program
|
|
329
|
+
* (see the SECURITY note in the body). Both the PDA derivation here and the
|
|
330
|
+
* `ant_program_id` arg the caller passes to the on-chain ix MUST agree (the
|
|
331
|
+
* handler re-derives and rejects mismatches).
|
|
294
332
|
*/
|
|
295
333
|
private _buildPrimaryNameValidationAccounts;
|
|
296
334
|
requestPrimaryName(params: ArNSPurchaseParams, _options?: WriteOptions): Promise<MessageResult>;
|
|
@@ -61,6 +61,35 @@ export declare function readAttribute(data: Uint8Array | ReadonlyUint8Array, key
|
|
|
61
61
|
* lookup once and caches the result.
|
|
62
62
|
*/
|
|
63
63
|
export declare function fetchAntProgramFromAsset(rpc: Parameters<typeof fetchEncodedAccount>[0], mint: Address, config?: FetchAccountConfig): Promise<Address | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Decide which ANT program id to trust for a SIGNING (write) transaction.
|
|
66
|
+
*
|
|
67
|
+
* The `ANT Program` trait returned by {@link fetchAntProgramFromAsset} is read
|
|
68
|
+
* from Metaplex Core asset / RPC data and is NOT authenticated — a malicious
|
|
69
|
+
* asset owner, or a spoofed/compromised RPC response, can set it to an
|
|
70
|
+
* attacker-controlled program. Write instructions use this value as the
|
|
71
|
+
* instruction `programAddress` while including the caller as a signer, so
|
|
72
|
+
* silently trusting a non-canonical detected value would let an attacker route
|
|
73
|
+
* a victim's signed transaction to their own program and inherit signer /
|
|
74
|
+
* writable privileges over the included accounts.
|
|
75
|
+
*
|
|
76
|
+
* Trust rules — BYO-ANT (ADR-016 / BD-100) stays supported, but only via
|
|
77
|
+
* explicit opt-in:
|
|
78
|
+
* - An `explicit` program id (the caller passed `antProgramId`) is always
|
|
79
|
+
* honored — the caller has taken responsibility for it.
|
|
80
|
+
* - Otherwise a detected value is honored only when it is `null` (no trait)
|
|
81
|
+
* or equals the canonical {@link ARIO_ANT_PROGRAM_ID}.
|
|
82
|
+
* - A detected NON-canonical value with no explicit opt-in throws, rather
|
|
83
|
+
* than silently signing against an untrusted program.
|
|
84
|
+
*
|
|
85
|
+
* Read-only paths never sign, so they may auto-detect freely; this guard is
|
|
86
|
+
* only for the write path.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveWriteAntProgram(args: {
|
|
89
|
+
explicit?: Address;
|
|
90
|
+
detected: Address | null;
|
|
91
|
+
canonical?: Address;
|
|
92
|
+
}): Address;
|
|
64
93
|
/**
|
|
65
94
|
* Fetch the current owner of an MPL Core asset. Returns `null` when the
|
|
66
95
|
* account doesn't exist or the AssetV1 layout can't be decoded.
|
package/lib/types/version.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ar.io/sdk",
|
|
3
|
-
"version": "4.0.0-solana.
|
|
3
|
+
"version": "4.0.0-solana.22",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ar-io/ar-io-sdk.git"
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"typescript": "^5.1.6"
|
|
123
123
|
},
|
|
124
124
|
"dependencies": {
|
|
125
|
-
"@ar.io/solana-contracts": "
|
|
125
|
+
"@ar.io/solana-contracts": "0.4.0",
|
|
126
126
|
"@solana-program/compute-budget": "^0.15.0",
|
|
127
127
|
"@solana-program/token": "^0.13.0",
|
|
128
128
|
"@solana/kit": "^6.8.0",
|