@ar.io/sdk 4.0.0-solana.16 → 4.0.0-solana.18

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.
@@ -16,11 +16,10 @@
16
16
  * Borsh codec, and account-meta wiring derived from the on-chain IDL.
17
17
  */
18
18
  import { fetchMaybeEscrowAnt, fetchMaybeEscrowToken, getCancelDepositInstruction, getCancelTokenDepositInstruction, getCancelVaultDepositInstruction, getClaimAntArweaveAttestedInstruction, getClaimAntEthereumInstruction, getClaimTokensArweaveAttestedInstruction, getClaimTokensEthereumInstruction, getClaimVaultArweaveAttestedInstruction, getClaimVaultEthereumInstruction, getDepositAntInstruction, getDepositTokensInstruction, getDepositVaultInstruction, getUpdateRecipientInstruction, getUpdateTokenRecipientInstruction, getUpdateVaultRecipientInstruction, } from '@ar.io/solana-contracts/ant-escrow';
19
- import { fetchMaybeVaultCounter, getVaultedTransferInstructionAsync, } from '@ar.io/solana-contracts/core';
20
19
  import { Logger } from '../common/logger.js';
21
20
  import { getAssociatedTokenAddressKit } from './ata.js';
22
21
  import { ARIO_ANT_ESCROW_PROGRAM_ID, ARIO_CORE_PROGRAM_ID, ESCROW_ARWEAVE_PUBKEY_LEN, ESCROW_ASSET_TYPE_VAULT, ESCROW_ETHEREUM_PUBKEY_LEN, ESCROW_PROTOCOL_ARWEAVE, ESCROW_PROTOCOL_ETHEREUM, } from './constants.js';
23
- import { getEscrowAntPDA, getEscrowTokenPDA, getEscrowVaultPDA, getVaultCounterPDA, getVaultPDA, } from './pda.js';
22
+ import { getEscrowAntPDA, getEscrowTokenPDA, getEscrowVaultPDA, } from './pda.js';
24
23
  import { sendAndConfirm } from './send.js';
25
24
  /** Map the Codama-generated `EscrowAnt` raw decoded type to our public
26
25
  * `EscrowAntState` with protocol enum + active-prefix pubkey slice. */
@@ -275,6 +274,28 @@ export class ANTEscrow {
275
274
  }
276
275
  }
277
276
  }
277
+ /**
278
+ * Pre-flight the on-chain `VaultStillLocked` gate (ADR-022): refuse to build
279
+ * a claim tx while the vault is still locked. Surfaces the unlock timestamp
280
+ * so callers / UIs can show "claimable after <date>" instead of a doomed tx.
281
+ *
282
+ * Exported for unit-testability; not part of the public SDK surface — call the
283
+ * high-level `claimVaultArweave` / `claimVaultEthereum` instead, which invoke
284
+ * this guard internally.
285
+ *
286
+ * @internal
287
+ */
288
+ export function assertVaultClaimable(escrow) {
289
+ const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
290
+ if (escrow.vaultEndTimestamp > nowSeconds) {
291
+ const unlockIso = new Date(Number(escrow.vaultEndTimestamp) * 1000).toISOString();
292
+ throw new Error(`Vault escrow is still locked until ${unlockIso} ` +
293
+ `(vault_end_timestamp=${escrow.vaultEndTimestamp}). ` +
294
+ `Active (still-locked) vault claims are not supported (ADR-022 / ` +
295
+ `VaultStillLocked) — wait until after the unlock timestamp, then ` +
296
+ `claim again to receive the tokens liquid.`);
297
+ }
298
+ }
278
299
  /** Map the Codama-generated `EscrowToken` raw decoded type to our public
279
300
  * `EscrowTokenState` with protocol enum + active-prefix pubkey slice. */
280
301
  function toEscrowTokenState(raw) {
@@ -537,23 +558,27 @@ export class TokenEscrow {
537
558
  }, { programAddress: this.programId });
538
559
  }
539
560
  /**
540
- * Submit an Arweave RSA-PSS-4096 signature to release escrowed vault tokens.
541
- *
542
- * The on-chain handler routes by vault state via instructions-sysvar
543
- * introspection: an expired vault sends tokens straight to
544
- * `claimantTokenAccount`; an active (still-locked) vault routes them to
545
- * `payerTokenAccount` and requires a matching `ario_core::vaulted_transfer`
546
- * sibling instruction in the same transaction.
561
+ * Submit an Arweave attestor's Ed25519 signature to release escrowed vault
562
+ * tokens. The on-chain handler delivers liquid tokens directly to
563
+ * `claimantTokenAccount`.
547
564
  *
548
- * This method auto-detects the path from `vaultEndTimestamp` and bundles
549
- * the sibling `vaulted_transfer` ix automatically when the vault is still
550
- * active callers do not need to construct it themselves.
565
+ * **Vaults are only claimable after `vault_end_timestamp`.** Active
566
+ * (still-locked) vault claims are rejected on-chain with `VaultStillLocked`
567
+ * (ADR-022 / BD-107: the former active re-lock path was removed because its
568
+ * sibling-`vaulted_transfer` introspection had no 1:1 claim↔re-lock binding
569
+ * → reuse / relayer skim). This method pre-flights the same gate and throws
570
+ * a clear `vault still locked until <ISO>` error rather than building a tx
571
+ * that will fail on-chain. To revive "claim early, stay locked" see the
572
+ * restoration playbook in the contracts repo
573
+ * (`docs/RESTORE_ACTIVE_VAULT_RELOCK.md`).
551
574
  */
552
575
  async claimVaultArweave(args) {
553
576
  const escrow = await this.requireVaultEscrow(args.depositor, args.assetId);
554
577
  if (escrow.recipientProtocol !== 'arweave') {
555
578
  throw new Error(`escrow recipient is ${escrow.recipientProtocol}, not arweave`);
556
579
  }
580
+ // ADR-022 / VaultStillLocked: pre-flight the on-chain lock gate.
581
+ assertVaultClaimable(escrow);
557
582
  const signer = this.requireSigner('claimVaultArweave');
558
583
  const [escrowPda] = await getEscrowVaultPDA(args.depositor, args.assetId, this.programId);
559
584
  // `args.signature` and `args.saltLen` are no longer fed to the
@@ -564,73 +589,56 @@ export class TokenEscrow {
564
589
  escrow: escrowPda,
565
590
  escrowTokenAccount: args.escrowTokenAccount,
566
591
  claimantTokenAccount: args.claimantTokenAccount,
567
- payerTokenAccount: args.payerTokenAccount,
568
592
  claimant: args.claimant,
569
593
  depositor: args.depositor,
570
594
  payer: signer,
571
595
  messageNonce: escrow.nonce,
572
596
  }, { programAddress: this.programId });
573
- const ixs = await this.maybeBundleVaultedTransfer(escrow, args, claimIx);
597
+ const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
598
+ const ixs = createClaimantAtaIx
599
+ ? [createClaimantAtaIx, claimIx]
600
+ : [claimIx];
574
601
  return this.send(ixs, 400_000);
575
602
  }
576
603
  /**
577
604
  * Submit an Ethereum ECDSA signature to release escrowed vault tokens. See
578
- * {@link claimVaultArweave} for the expired/active vault routing semantics.
579
- * Auto-bundles the sibling `vaulted_transfer` ix for active vaults.
605
+ * {@link claimVaultArweave} same lock semantics: vaults are only claimable
606
+ * after `vault_end_timestamp`; active (still-locked) claims throw pre-flight
607
+ * and are rejected on-chain with `VaultStillLocked` (ADR-022 / BD-107).
580
608
  */
581
609
  async claimVaultEthereum(args) {
582
610
  const escrow = await this.requireVaultEscrow(args.depositor, args.assetId);
583
611
  if (escrow.recipientProtocol !== 'ethereum') {
584
612
  throw new Error(`escrow recipient is ${escrow.recipientProtocol}, not ethereum`);
585
613
  }
614
+ assertVaultClaimable(escrow);
586
615
  const signer = this.requireSigner('claimVaultEthereum');
587
616
  const [escrowPda] = await getEscrowVaultPDA(args.depositor, args.assetId, this.programId);
588
617
  const claimIx = getClaimVaultEthereumInstruction({
589
618
  escrow: escrowPda,
590
619
  escrowTokenAccount: args.escrowTokenAccount,
591
620
  claimantTokenAccount: args.claimantTokenAccount,
592
- payerTokenAccount: args.payerTokenAccount,
593
621
  claimant: args.claimant,
594
622
  depositor: args.depositor,
595
623
  payer: signer,
596
624
  messageNonce: escrow.nonce,
597
625
  signature: args.signature,
598
626
  }, { programAddress: this.programId });
599
- const ixs = await this.maybeBundleVaultedTransfer(escrow, args, claimIx);
627
+ const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
628
+ const ixs = createClaimantAtaIx
629
+ ? [createClaimantAtaIx, claimIx]
630
+ : [claimIx];
600
631
  return this.send(ixs);
601
632
  }
602
633
  /**
603
- * If the vault escrow is still locked (`vaultEndTimestamp` in the future),
604
- * return `[claimIx, vaultedTransferIx]` so the on-chain claim handler can
605
- * see the sibling (via `instructions_sysvar`) and re-vault tokens for the
606
- * claimant. If the vault has expired, just `[claimIx]` — the on-chain
607
- * handler delivers tokens directly to `claimantTokenAccount`.
608
- *
609
- * **Ordering matters at runtime**: `claim` must execute first so it can
610
- * move tokens `escrow → payerTokenAccount`. `vaulted_transfer` then pulls
611
- * from `payerTokenAccount` into the new vault. Reversing the order makes
612
- * `vaulted_transfer` fail with `insufficient funds` because nothing has
613
- * funded `payerTokenAccount` yet. The introspection check in
614
- * `vault_introspect::verify_vaulted_transfer_in_tx` is presence-based
615
- * (reads `instructions_sysvar`) so the *sibling* ordering doesn't matter
616
- * for the check itself — only for atomic execution.
617
- *
618
- * `lockDurationSeconds` is set to the SDK-local `remaining` value. The
619
- * on-chain handler accepts `lock_duration >= remaining_at_execution - 60s`
620
- * (see the introspection function's tolerance), so any modest clock skew
621
- * between client and chain is absorbed.
622
- */
623
634
  /**
624
635
  * Idempotent-create the claimant's canonical ATA when needed.
625
636
  *
626
- * The on-chain claim handler delivers liquid tokens directly to
627
- * `claimantTokenAccount` for expired vaults AND for token escrows.
628
- * For active vaults, the `claim_vault_*` Anchor Accounts struct still
629
- * declares `claimant_token_account: Account<TokenAccount>`, which forces
630
- * Anchor's account-load-time validation to require the account exist
631
- * even though the active path doesn't write to it. Either way: if the
632
- * claimant is a fresh wallet that has never held this mint, the ATA
633
- * doesn't exist and the tx fails with `AccountNotInitialized` (#3012).
637
+ * The claim handler delivers liquid tokens directly to
638
+ * `claimantTokenAccount` (post-ADR-022 there's only the liquid path for
639
+ * vaults). If the claimant is a fresh wallet that has never held this
640
+ * mint, the ATA doesn't exist and the tx fails with `AccountNotInitialized`
641
+ * (#3012).
634
642
  *
635
643
  * Returns `null` when the caller passed a non-canonical
636
644
  * `claimantTokenAccount` (manually-created non-ATA token account,
@@ -643,52 +651,6 @@ export class TokenEscrow {
643
651
  const signer = this.requireSigner('createClaimantAtaIfCanonical');
644
652
  return buildCreateAtaIdempotentIx(signer.address, canonical, claimant, mint);
645
653
  }
646
- async maybeBundleVaultedTransfer(escrow, args, claimIx) {
647
- const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
648
- const remaining = escrow.vaultEndTimestamp - nowSeconds;
649
- if (remaining <= 0n) {
650
- // Expired vault → claim handler delivers liquid to claimantTokenAccount.
651
- // Idempotent-create that ATA if it's the canonical derivation so a
652
- // first-time recipient just works.
653
- const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
654
- return createClaimantAtaIx ? [createClaimantAtaIx, claimIx] : [claimIx];
655
- }
656
- const signer = this.requireSigner('maybeBundleVaultedTransfer');
657
- const nextId = await this.getNextVaultId(args.claimant);
658
- const [vaultPda] = await getVaultPDA(args.claimant, nextId, this.coreProgram);
659
- const vaultATA = await getAssociatedTokenAddressKit(escrow.arioMint, vaultPda, true);
660
- // Active-vault path: `claim_vault_*` still validates the claimant ATA
661
- // at account-load-time (Anchor `Account<TokenAccount>` constraint),
662
- // even though no liquid is written to it. Idempotent-create so a fresh
663
- // claimant doesn't fail the ix with AccountNotInitialized (#3012).
664
- const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
665
- // The new vault PDA's ATA must exist before `vaulted_transfer` reads it
666
- // (else `AccountNotInitialized` #3012). Idempotent so a retry after a
667
- // partial-failure tx is safe. Placed after the claim ix to preserve
668
- // the "claim first" tx ordering invariant.
669
- const createVaultAtaIx = buildCreateAtaIdempotentIx(signer.address, vaultATA, vaultPda, escrow.arioMint);
670
- const vaultedIx = await getVaultedTransferInstructionAsync({
671
- vault: vaultPda,
672
- senderTokenAccount: args.payerTokenAccount,
673
- vaultTokenAccount: vaultATA,
674
- recipient: args.claimant,
675
- sender: signer,
676
- amount: escrow.amount,
677
- lockDurationSeconds: remaining,
678
- revocable: escrow.vaultRevocable,
679
- }, { programAddress: this.coreProgram });
680
- const head = createClaimantAtaIx ? [createClaimantAtaIx] : [];
681
- return [...head, claimIx, createVaultAtaIx, vaultedIx];
682
- }
683
- /** Read the recipient's `VaultCounter.nextId`, defaulting to 0n if the
684
- * counter PDA hasn't been initialised yet (first vault for that owner). */
685
- async getNextVaultId(owner) {
686
- const [counterPda] = await getVaultCounterPDA(owner, this.coreProgram);
687
- const account = await fetchMaybeVaultCounter(this.rpc, counterPda, {
688
- commitment: this.commitment,
689
- });
690
- return account.exists ? account.data.nextId : 0n;
691
- }
692
654
  // -------------------------------------------------------------------
693
655
  // Write — cancel
694
656
  // -------------------------------------------------------------------
@@ -1786,39 +1786,44 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
1786
1786
  async reassignName(params, _options) {
1787
1787
  const newAnt = address(params.processId);
1788
1788
  const [arnsRecord] = await getArnsRecordPDA(params.name, this.arnsProgram);
1789
+ // The on-chain `reassign_name` (PR #73 / BD-106 / BD-095) now authorizes
1790
+ // against the CURRENT Metaplex Core holder of `record.ant` via a named
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);
1789
1796
  const ix = await getReassignNameInstructionAsync(await this.withArnsDefaults({
1790
1797
  arnsRecord,
1798
+ antAsset: currentAnt,
1791
1799
  caller: this.signer,
1792
1800
  newAnt,
1793
1801
  }), { programAddress: this.arnsProgram });
1794
- // The on-chain handler validates the new ANT via `remaining_accounts[0]`
1795
- // (must be MPL-Core-owned + key matches `newAnt`). The codama builder
1796
- // for reassign_name only encodes the typed accounts above, so we tag
1797
- // the new ANT asset on as a readonly remaining account.
1798
- //
1799
- // Sprint 4 / ADR-016: post-reassign the record points at `newAnt`. The
1800
- // bundled `sync_attributes` MUST target `newAnt` without the
1801
- // override, the helper would read the on-chain record at SDK build
1802
- // time (still pointing at the OLD asset), build a sync ix for the
1803
- // OLD asset, and fail the post-reassign `record.ant == asset.key()`
1804
- // check. The owner-check inside _buildSyncAttributesIxIfOwner runs
1805
- // against `newAnt`, so the bundle fires only when the reassign
1806
- // caller is also the new ANT's holder; otherwise the ix is sent
1807
- // alone and the new owner runs `syncAttributes()` later (BD-095/096).
1802
+ // Post-reassign the record points at `newAnt`. The bundled
1803
+ // `sync_attributes` MUST target `newAnt` without the override, the
1804
+ // helper would read the on-chain record at SDK build time (still
1805
+ // pointing at the OLD asset), build a sync ix for the OLD asset, and
1806
+ // fail the post-reassign `record.ant == asset.key()` check. The
1807
+ // owner-check inside _buildSyncAttributesIxIfOwner runs against
1808
+ // `newAnt`, so the bundle fires only when the reassign caller is also
1809
+ // the new ANT's holder; otherwise the ix is sent alone and the new
1810
+ // owner runs `syncAttributes()` later (BD-095/096).
1808
1811
  const syncIx = await this._buildSyncAttributesIxIfOwner(params.name, newAnt);
1809
- const reassignWithMetas = withRemainingAccounts(ix, [
1810
- { address: newAnt, role: AccountRole.READONLY },
1811
- ]);
1812
- const sig = await this.sendTransaction(syncIx ? [reassignWithMetas, syncIx] : [reassignWithMetas]);
1812
+ const sig = await this.sendTransaction(syncIx ? [ix, syncIx] : [ix]);
1813
1813
  return { id: sig };
1814
1814
  }
1815
1815
  /** Release a permabuy name back to the registry (creates a returned name auction). */
1816
1816
  async releaseName(params, _options) {
1817
1817
  const [returnedNamePda] = await getReturnedNamePDA(params.name, this.arnsProgram);
1818
1818
  const [arnsRecord] = await getArnsRecordPDA(params.name, this.arnsProgram);
1819
+ // PR #73 / BD-106: `release_name` now authorizes against the current
1820
+ // Metaplex Core holder of `record.ant` via a named `ant_asset` account
1821
+ // constrained to `arns_record.ant`. Fetch the record to know which.
1822
+ const currentRecord = await this.getArNSRecord({ name: params.name });
1819
1823
  const ix = await getReleaseNameInstructionAsync(await this.withArnsDefaults({
1820
1824
  arnsRecord,
1821
1825
  returnedName: returnedNamePda,
1826
+ antAsset: address(currentRecord.processId),
1822
1827
  caller: this.signer,
1823
1828
  }), { programAddress: this.arnsProgram });
1824
1829
  // Note: no sync_attributes bundle here — release_name closes the
@@ -14,4 +14,4 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
17
- export const version = '4.0.0-solana.16';
17
+ export const version = '4.0.0-solana.18';
@@ -16,15 +16,13 @@
16
16
  * Borsh codec, and account-meta wiring derived from the on-chain IDL.
17
17
  */
18
18
  import { type Address, type Commitment, type Instruction, type TransactionSigner } from '@solana/kit';
19
+ import { type EscrowAnt, type EscrowToken } from '@ar.io/solana-contracts/ant-escrow';
19
20
  import type { ILogger } from '../common/logger.js';
20
21
  import type { SolanaRpc, SolanaRpcSubscriptions } from './types.js';
21
22
  export type EscrowProtocol = 'arweave' | 'ethereum';
22
23
  export interface EscrowAntState {
23
- version: {
24
- major: number;
25
- minor: number;
26
- patch: number;
27
- };
24
+ /** On-chain schema version, as decoded by the generated client. */
25
+ version: EscrowAnt['version'];
28
26
  bump: number;
29
27
  depositor: Address;
30
28
  antMint: Address;
@@ -39,9 +37,13 @@ export interface ANTEscrowConfig {
39
37
  signer?: TransactionSigner;
40
38
  programId?: Address;
41
39
  /**
42
- * ario-core program id, used by `TokenEscrow` to build the sibling
43
- * `vaulted_transfer` instruction when claiming an active (still-locked)
44
- * vault escrow. Defaults to {@link ARIO_CORE_PROGRAM_ID}.
40
+ * ario-core program id. Currently unused (post-ADR-022 the SDK no longer
41
+ * builds a sibling `vaulted_transfer`; active vault claims are rejected
42
+ * with `VaultStillLocked`). Retained for forward-compat — if the active
43
+ * re-lock path is ever revived via the direct-CPI restoration playbook
44
+ * (contracts `docs/RESTORE_ACTIVE_VAULT_RELOCK.md`), this is the program
45
+ * the new claim ABI would need to reference. Defaults to
46
+ * {@link ARIO_CORE_PROGRAM_ID}.
45
47
  */
46
48
  coreProgram?: Address;
47
49
  commitment?: Commitment;
@@ -168,11 +170,8 @@ export declare class ANTEscrow {
168
170
  }
169
171
  export type EscrowAssetType = 'token' | 'vault';
170
172
  export interface EscrowTokenState {
171
- version: {
172
- major: number;
173
- minor: number;
174
- patch: number;
175
- };
173
+ /** On-chain schema version, as decoded by the generated client. */
174
+ version: EscrowToken['version'];
176
175
  bump: number;
177
176
  depositor: Address;
178
177
  assetType: EscrowAssetType;
@@ -186,6 +185,18 @@ export interface EscrowTokenState {
186
185
  vaultEndTimestamp: bigint;
187
186
  vaultRevocable: boolean;
188
187
  }
188
+ /**
189
+ * Pre-flight the on-chain `VaultStillLocked` gate (ADR-022): refuse to build
190
+ * a claim tx while the vault is still locked. Surfaces the unlock timestamp
191
+ * so callers / UIs can show "claimable after <date>" instead of a doomed tx.
192
+ *
193
+ * Exported for unit-testability; not part of the public SDK surface — call the
194
+ * high-level `claimVaultArweave` / `claimVaultEthereum` instead, which invoke
195
+ * this guard internally.
196
+ *
197
+ * @internal
198
+ */
199
+ export declare function assertVaultClaimable(escrow: EscrowTokenState): void;
189
200
  /**
190
201
  * Solana-backed client for the trustless token/vault escrow program. All
191
202
  * write methods require both `rpcSubscriptions` and `signer`; read methods
@@ -302,17 +313,19 @@ export declare class TokenEscrow {
302
313
  messageNonce: Uint8Array;
303
314
  }): Promise<Instruction>;
304
315
  /**
305
- * Submit an Arweave RSA-PSS-4096 signature to release escrowed vault tokens.
316
+ * Submit an Arweave attestor's Ed25519 signature to release escrowed vault
317
+ * tokens. The on-chain handler delivers liquid tokens directly to
318
+ * `claimantTokenAccount`.
306
319
  *
307
- * The on-chain handler routes by vault state via instructions-sysvar
308
- * introspection: an expired vault sends tokens straight to
309
- * `claimantTokenAccount`; an active (still-locked) vault routes them to
310
- * `payerTokenAccount` and requires a matching `ario_core::vaulted_transfer`
311
- * sibling instruction in the same transaction.
312
- *
313
- * This method auto-detects the path from `vaultEndTimestamp` and bundles
314
- * the sibling `vaulted_transfer` ix automatically when the vault is still
315
- * active — callers do not need to construct it themselves.
320
+ * **Vaults are only claimable after `vault_end_timestamp`.** Active
321
+ * (still-locked) vault claims are rejected on-chain with `VaultStillLocked`
322
+ * (ADR-022 / BD-107: the former active re-lock path was removed because its
323
+ * sibling-`vaulted_transfer` introspection had no 1:1 claim↔re-lock binding
324
+ * reuse / relayer skim). This method pre-flights the same gate and throws
325
+ * a clear `vault still locked until <ISO>` error rather than building a tx
326
+ * that will fail on-chain. To revive "claim early, stay locked" see the
327
+ * restoration playbook in the contracts repo
328
+ * (`docs/RESTORE_ACTIVE_VAULT_RELOCK.md`).
316
329
  */
317
330
  claimVaultArweave(args: {
318
331
  depositor: Address;
@@ -320,14 +333,14 @@ export declare class TokenEscrow {
320
333
  claimant: Address;
321
334
  claimantTokenAccount: Address;
322
335
  escrowTokenAccount: Address;
323
- payerTokenAccount: Address;
324
336
  signature: Uint8Array;
325
337
  saltLen?: number;
326
338
  }): Promise<string>;
327
339
  /**
328
340
  * Submit an Ethereum ECDSA signature to release escrowed vault tokens. See
329
- * {@link claimVaultArweave} for the expired/active vault routing semantics.
330
- * Auto-bundles the sibling `vaulted_transfer` ix for active vaults.
341
+ * {@link claimVaultArweave} same lock semantics: vaults are only claimable
342
+ * after `vault_end_timestamp`; active (still-locked) claims throw pre-flight
343
+ * and are rejected on-chain with `VaultStillLocked` (ADR-022 / BD-107).
331
344
  */
332
345
  claimVaultEthereum(args: {
333
346
  depositor: Address;
@@ -335,51 +348,23 @@ export declare class TokenEscrow {
335
348
  claimant: Address;
336
349
  claimantTokenAccount: Address;
337
350
  escrowTokenAccount: Address;
338
- payerTokenAccount: Address;
339
351
  signature: Uint8Array;
340
352
  }): Promise<string>;
341
353
  /**
342
- * If the vault escrow is still locked (`vaultEndTimestamp` in the future),
343
- * return `[claimIx, vaultedTransferIx]` so the on-chain claim handler can
344
- * see the sibling (via `instructions_sysvar`) and re-vault tokens for the
345
- * claimant. If the vault has expired, just `[claimIx]` — the on-chain
346
- * handler delivers tokens directly to `claimantTokenAccount`.
347
- *
348
- * **Ordering matters at runtime**: `claim` must execute first so it can
349
- * move tokens `escrow → payerTokenAccount`. `vaulted_transfer` then pulls
350
- * from `payerTokenAccount` into the new vault. Reversing the order makes
351
- * `vaulted_transfer` fail with `insufficient funds` because nothing has
352
- * funded `payerTokenAccount` yet. The introspection check in
353
- * `vault_introspect::verify_vaulted_transfer_in_tx` is presence-based
354
- * (reads `instructions_sysvar`) so the *sibling* ordering doesn't matter
355
- * for the check itself — only for atomic execution.
356
- *
357
- * `lockDurationSeconds` is set to the SDK-local `remaining` value. The
358
- * on-chain handler accepts `lock_duration >= remaining_at_execution - 60s`
359
- * (see the introspection function's tolerance), so any modest clock skew
360
- * between client and chain is absorbed.
361
- */
362
354
  /**
363
355
  * Idempotent-create the claimant's canonical ATA when needed.
364
356
  *
365
- * The on-chain claim handler delivers liquid tokens directly to
366
- * `claimantTokenAccount` for expired vaults AND for token escrows.
367
- * For active vaults, the `claim_vault_*` Anchor Accounts struct still
368
- * declares `claimant_token_account: Account<TokenAccount>`, which forces
369
- * Anchor's account-load-time validation to require the account exist
370
- * even though the active path doesn't write to it. Either way: if the
371
- * claimant is a fresh wallet that has never held this mint, the ATA
372
- * doesn't exist and the tx fails with `AccountNotInitialized` (#3012).
357
+ * The claim handler delivers liquid tokens directly to
358
+ * `claimantTokenAccount` (post-ADR-022 there's only the liquid path for
359
+ * vaults). If the claimant is a fresh wallet that has never held this
360
+ * mint, the ATA doesn't exist and the tx fails with `AccountNotInitialized`
361
+ * (#3012).
373
362
  *
374
363
  * Returns `null` when the caller passed a non-canonical
375
364
  * `claimantTokenAccount` (manually-created non-ATA token account,
376
365
  * presumably already exists — caller's responsibility).
377
366
  */
378
367
  private _createClaimantAtaIfCanonical;
379
- private maybeBundleVaultedTransfer;
380
- /** Read the recipient's `VaultCounter.nextId`, defaulting to 0n if the
381
- * counter PDA hasn't been initialised yet (first vault for that owner). */
382
- private getNextVaultId;
383
368
  /**
384
369
  * Cancel a token or vault escrow deposit and return the tokens to the
385
370
  * depositor. Only callable by the original depositor.
@@ -13,4 +13,4 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- export declare const version = "4.0.0-solana.15";
16
+ export declare const version = "4.0.0-solana.17";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar.io/sdk",
3
- "version": "4.0.0-solana.16",
3
+ "version": "4.0.0-solana.18",
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": "^0.3.0",
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",