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

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.
@@ -274,10 +274,36 @@ export class ANTEscrow {
274
274
  }
275
275
  }
276
276
  }
277
+ /**
278
+ * Forward clock-skew buffer (seconds) added to `vault_end_timestamp` before
279
+ * the SDK considers a vault claimable. The SDK reads wall-clock time
280
+ * (`Date.now()`) while the on-chain gate reads Solana cluster time, and
281
+ * the two can disagree by several seconds. The buffer biases every skew
282
+ * race into the *friendly* direction: the SDK rejects when the chain
283
+ * would actually accept (user retries 30s later, succeeds), never the
284
+ * reverse (user submits a doomed tx and sees the raw on-chain error).
285
+ *
286
+ * 30s is conservative — Solana cluster clock typically drifts <2s vs
287
+ * wall clock — but matches the order of magnitude of the previously-used
288
+ * `60s` introspection tolerance in the removed `vault_introspect` module.
289
+ */
290
+ export const CLOCK_SKEW_TOLERANCE_SECONDS = 30n;
291
+ /**
292
+ * Returns `true` when a vault escrow is past its unlock timestamp by at
293
+ * least {@link CLOCK_SKEW_TOLERANCE_SECONDS}. Non-throwing companion to
294
+ * {@link assertVaultClaimable} for UI gating (e.g. enabling/disabling a
295
+ * Submit button without showing an error).
296
+ */
297
+ export function isVaultClaimable(escrow) {
298
+ const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
299
+ return nowSeconds >= escrow.vaultEndTimestamp + CLOCK_SKEW_TOLERANCE_SECONDS;
300
+ }
277
301
  /**
278
302
  * 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.
303
+ * a claim tx while the vault is still locked, with a small forward
304
+ * {@link CLOCK_SKEW_TOLERANCE_SECONDS} buffer so wall/cluster clock skew
305
+ * biases into the friendly direction. Surfaces the unlock timestamp so
306
+ * callers / UIs can show "claimable after <date>" instead of a doomed tx.
281
307
  *
282
308
  * Exported for unit-testability; not part of the public SDK surface — call the
283
309
  * high-level `claimVaultArweave` / `claimVaultEthereum` instead, which invoke
@@ -286,14 +312,15 @@ export class ANTEscrow {
286
312
  * @internal
287
313
  */
288
314
  export function assertVaultClaimable(escrow) {
289
- const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
290
- if (escrow.vaultEndTimestamp > nowSeconds) {
315
+ if (!isVaultClaimable(escrow)) {
291
316
  const unlockIso = new Date(Number(escrow.vaultEndTimestamp) * 1000).toISOString();
292
317
  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.`);
318
+ `(vault_end_timestamp=${escrow.vaultEndTimestamp}; ` +
319
+ `the SDK adds a ${CLOCK_SKEW_TOLERANCE_SECONDS}s clock-skew buffer ` +
320
+ `before allowing a claim). Active (still-locked) vault claims are ` +
321
+ `rejected on-chain with VaultStillLocked (ADR-022) wait until ` +
322
+ `after the unlock timestamp + buffer, then claim again to receive ` +
323
+ `the tokens liquid.`);
297
324
  }
298
325
  }
299
326
  /** Map the Codama-generated `EscrowToken` raw decoded type to our public
@@ -558,47 +585,85 @@ export class TokenEscrow {
558
585
  }, { programAddress: this.programId });
559
586
  }
560
587
  /**
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`.
588
+ * **Use {@link claimVaultArweaveIx} instead** this single-send wrapper
589
+ * cannot work end-to-end for the Arweave attested vault-claim path,
590
+ * because the on-chain `claim_vault_arweave_attested` handler requires
591
+ * an Ed25519Program native sigverify ix at idx-1 of the claim ix
592
+ * (introspected via `instructions_sysvar`). That sigverify ix carries
593
+ * the attestor's Ed25519 signature over the canonical claim message
594
+ * and is built by the *integrator* (who calls the off-chain attestor
595
+ * service for the signature, per ADR-017) — the SDK has no attestor
596
+ * URL or client to do this for the caller.
597
+ *
598
+ * Calling this method instead of composing via
599
+ * {@link claimVaultArweaveIx} will hit `MissingAttestation` on-chain.
600
+ * It is kept only for ABI continuity; new code must use
601
+ * {@link claimVaultArweaveIx} and prepend the attestor sigverify ix.
564
602
  *
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`).
603
+ * @deprecated cannot succeed alone see {@link claimVaultArweaveIx}.
574
604
  */
575
- async claimVaultArweave(args) {
576
- const escrow = await this.requireVaultEscrow(args.depositor, args.assetId);
577
- if (escrow.recipientProtocol !== 'arweave') {
578
- throw new Error(`escrow recipient is ${escrow.recipientProtocol}, not arweave`);
605
+ async claimVaultArweave(_args) {
606
+ throw new Error('claimVaultArweave cannot complete the Arweave attested vault-claim ' +
607
+ 'in a single SDK call: the on-chain handler requires an ' +
608
+ 'Ed25519Program sigverify ix at idx-1 of the claim ix, which ' +
609
+ 'must carry the attestor service’s signature over the canonical ' +
610
+ 'claim message (ADR-017). The SDK does not know your attestor URL. ' +
611
+ 'Use claimVaultArweaveIx() instead and bundle the sigverify ix ' +
612
+ 'yourself: ' +
613
+ '[createAtaIx?, ed25519SigverifyIx, claimVaultArweaveIx, ...]. ' +
614
+ 'See ar-io-solana-escrow-app/src/pages/ClaimPage.tsx for the ' +
615
+ 'reference composition, and ADR-022 / VaultStillLocked for the ' +
616
+ 'still-locked rejection (use isVaultClaimable() to pre-flight).');
617
+ }
618
+ /**
619
+ * Build a `claim_vault_arweave_attested` instruction (without sending).
620
+ * Mirror of {@link claimTokensArweaveIx} for the vault-claim path:
621
+ * returns the bare claim ix so the caller can prepend the attestor's
622
+ * Ed25519Program sigverify ix and submit the bundle in one tx.
623
+ *
624
+ * The on-chain handler:
625
+ * - Reads the preceding Ed25519Program native sigverify ix from
626
+ * `instructions_sysvar` to confirm the attestor signed the canonical
627
+ * claim message.
628
+ * - Rejects with `VaultStillLocked` if `clock < vault_end_timestamp`
629
+ * (ADR-022). Callers should pre-flight with {@link isVaultClaimable}
630
+ * (non-throwing) or {@link assertVaultClaimable} (throws with the
631
+ * unlock timestamp) before composing the tx.
632
+ * - On the expired path, transfers the escrowed amount liquid to
633
+ * `claimantTokenAccount` and closes the escrow PDA.
634
+ *
635
+ * Composition (frontend pattern):
636
+ * ```ts
637
+ * const escrow = await tokenEscrow.requireVaultEscrow(depositor, assetId);
638
+ * assertVaultClaimable(escrow); // pre-flight
639
+ * const canonical = canonicalMessage({ ... }); // build message
640
+ * const attestation = await attestor.attest({ ... }); // attestor service
641
+ * const ed25519Ix = buildEd25519SigverifyIx(
642
+ * attestation.attestorPubkey, attestation.signature, canonical,
643
+ * );
644
+ * const claimIx = await tokenEscrow.claimVaultArweaveIx({
645
+ * depositor, assetId, claimant, claimantTokenAccount,
646
+ * escrowTokenAccount, messageNonce: escrow.nonce,
647
+ * });
648
+ * // Idempotent-create the claimant ATA if it's the canonical derivation.
649
+ * await sendTx([createAtaIx?, ed25519Ix, claimIx]);
650
+ * ```
651
+ */
652
+ async claimVaultArweaveIx(args) {
653
+ if (args.messageNonce.length !== 32) {
654
+ throw new Error('messageNonce must be 32 bytes');
579
655
  }
580
- // ADR-022 / VaultStillLocked: pre-flight the on-chain lock gate.
581
- assertVaultClaimable(escrow);
582
- const signer = this.requireSigner('claimVaultArweave');
656
+ const signer = this.requireSigner('claimVaultArweaveIx');
583
657
  const [escrowPda] = await getEscrowVaultPDA(args.depositor, args.assetId, this.programId);
584
- // `args.signature` and `args.saltLen` are no longer fed to the
585
- // builder — the on-chain `claim_vault_arweave_attested` ix verifies
586
- // the attestor's Ed25519 signature via instruction-introspection
587
- // of a preceding sigverify ix. See doc on `claimArweaveIx`.
588
- const claimIx = getClaimVaultArweaveAttestedInstruction({
658
+ return getClaimVaultArweaveAttestedInstruction({
589
659
  escrow: escrowPda,
590
660
  escrowTokenAccount: args.escrowTokenAccount,
591
661
  claimantTokenAccount: args.claimantTokenAccount,
592
662
  claimant: args.claimant,
593
663
  depositor: args.depositor,
594
664
  payer: signer,
595
- messageNonce: escrow.nonce,
665
+ messageNonce: args.messageNonce,
596
666
  }, { programAddress: this.programId });
597
- const createClaimantAtaIx = await this._createClaimantAtaIfCanonical(args.claimant, args.claimantTokenAccount, escrow.arioMint);
598
- const ixs = createClaimantAtaIx
599
- ? [createClaimantAtaIx, claimIx]
600
- : [claimIx];
601
- return this.send(ixs, 400_000);
602
667
  }
603
668
  /**
604
669
  * Submit an Ethereum ECDSA signature to release escrowed vault tokens. See
@@ -68,7 +68,13 @@ export { SolanaANTWriteable } from './ant-writeable.js';
68
68
  export { SolanaANTRegistryReadable } from './ant-registry-readable.js';
69
69
  export { SolanaANTRegistryWriteable } from './ant-registry-writeable.js';
70
70
  // ANT-escrow client (trustless multi-protocol custody — Arweave RSA-PSS / Ethereum ECDSA)
71
- export { ANTEscrow, TokenEscrow } from './escrow.js';
71
+ export { ANTEscrow, TokenEscrow,
72
+ // Vault-claim pre-flight helpers (ADR-022 / VaultStillLocked).
73
+ // Exported so downstream UIs can gate their Submit buttons using the
74
+ // SAME forward CLOCK_SKEW_TOLERANCE_SECONDS buffer the SDK's
75
+ // assertVaultClaimable throws use — keeping pre-flight and UI gates
76
+ // in lock-step so users never see a raw on-chain error.
77
+ assertVaultClaimable, isVaultClaimable, CLOCK_SKEW_TOLERANCE_SECONDS, } from './escrow.js';
72
78
  // Canonical claim-message helper (byte-equivalent to Rust impl)
73
79
  export { canonicalMessage, canonicalMessageV2, bytesToHexLower, } from './canonical-message.js';
74
80
  // ANT spawn (mint MPL Core asset + initialize ario-ant state in one tx)
@@ -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.18';
17
+ export const version = '4.0.0-solana.20';
@@ -185,10 +185,33 @@ export interface EscrowTokenState {
185
185
  vaultEndTimestamp: bigint;
186
186
  vaultRevocable: boolean;
187
187
  }
188
+ /**
189
+ * Forward clock-skew buffer (seconds) added to `vault_end_timestamp` before
190
+ * the SDK considers a vault claimable. The SDK reads wall-clock time
191
+ * (`Date.now()`) while the on-chain gate reads Solana cluster time, and
192
+ * the two can disagree by several seconds. The buffer biases every skew
193
+ * race into the *friendly* direction: the SDK rejects when the chain
194
+ * would actually accept (user retries 30s later, succeeds), never the
195
+ * reverse (user submits a doomed tx and sees the raw on-chain error).
196
+ *
197
+ * 30s is conservative — Solana cluster clock typically drifts <2s vs
198
+ * wall clock — but matches the order of magnitude of the previously-used
199
+ * `60s` introspection tolerance in the removed `vault_introspect` module.
200
+ */
201
+ export declare const CLOCK_SKEW_TOLERANCE_SECONDS = 30n;
202
+ /**
203
+ * Returns `true` when a vault escrow is past its unlock timestamp by at
204
+ * least {@link CLOCK_SKEW_TOLERANCE_SECONDS}. Non-throwing companion to
205
+ * {@link assertVaultClaimable} for UI gating (e.g. enabling/disabling a
206
+ * Submit button without showing an error).
207
+ */
208
+ export declare function isVaultClaimable(escrow: EscrowTokenState): boolean;
188
209
  /**
189
210
  * 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.
211
+ * a claim tx while the vault is still locked, with a small forward
212
+ * {@link CLOCK_SKEW_TOLERANCE_SECONDS} buffer so wall/cluster clock skew
213
+ * biases into the friendly direction. Surfaces the unlock timestamp so
214
+ * callers / UIs can show "claimable after <date>" instead of a doomed tx.
192
215
  *
193
216
  * Exported for unit-testability; not part of the public SDK surface — call the
194
217
  * high-level `claimVaultArweave` / `claimVaultEthereum` instead, which invoke
@@ -313,21 +336,24 @@ export declare class TokenEscrow {
313
336
  messageNonce: Uint8Array;
314
337
  }): Promise<Instruction>;
315
338
  /**
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`.
339
+ * **Use {@link claimVaultArweaveIx} instead** this single-send wrapper
340
+ * cannot work end-to-end for the Arweave attested vault-claim path,
341
+ * because the on-chain `claim_vault_arweave_attested` handler requires
342
+ * an Ed25519Program native sigverify ix at idx-1 of the claim ix
343
+ * (introspected via `instructions_sysvar`). That sigverify ix carries
344
+ * the attestor's Ed25519 signature over the canonical claim message
345
+ * and is built by the *integrator* (who calls the off-chain attestor
346
+ * service for the signature, per ADR-017) — the SDK has no attestor
347
+ * URL or client to do this for the caller.
319
348
  *
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`).
349
+ * Calling this method instead of composing via
350
+ * {@link claimVaultArweaveIx} will hit `MissingAttestation` on-chain.
351
+ * It is kept only for ABI continuity; new code must use
352
+ * {@link claimVaultArweaveIx} and prepend the attestor sigverify ix.
353
+ *
354
+ * @deprecated cannot succeed alone see {@link claimVaultArweaveIx}.
329
355
  */
330
- claimVaultArweave(args: {
356
+ claimVaultArweave(_args: {
331
357
  depositor: Address;
332
358
  assetId: Uint8Array;
333
359
  claimant: Address;
@@ -336,6 +362,49 @@ export declare class TokenEscrow {
336
362
  signature: Uint8Array;
337
363
  saltLen?: number;
338
364
  }): Promise<string>;
365
+ /**
366
+ * Build a `claim_vault_arweave_attested` instruction (without sending).
367
+ * Mirror of {@link claimTokensArweaveIx} for the vault-claim path:
368
+ * returns the bare claim ix so the caller can prepend the attestor's
369
+ * Ed25519Program sigverify ix and submit the bundle in one tx.
370
+ *
371
+ * The on-chain handler:
372
+ * - Reads the preceding Ed25519Program native sigverify ix from
373
+ * `instructions_sysvar` to confirm the attestor signed the canonical
374
+ * claim message.
375
+ * - Rejects with `VaultStillLocked` if `clock < vault_end_timestamp`
376
+ * (ADR-022). Callers should pre-flight with {@link isVaultClaimable}
377
+ * (non-throwing) or {@link assertVaultClaimable} (throws with the
378
+ * unlock timestamp) before composing the tx.
379
+ * - On the expired path, transfers the escrowed amount liquid to
380
+ * `claimantTokenAccount` and closes the escrow PDA.
381
+ *
382
+ * Composition (frontend pattern):
383
+ * ```ts
384
+ * const escrow = await tokenEscrow.requireVaultEscrow(depositor, assetId);
385
+ * assertVaultClaimable(escrow); // pre-flight
386
+ * const canonical = canonicalMessage({ ... }); // build message
387
+ * const attestation = await attestor.attest({ ... }); // attestor service
388
+ * const ed25519Ix = buildEd25519SigverifyIx(
389
+ * attestation.attestorPubkey, attestation.signature, canonical,
390
+ * );
391
+ * const claimIx = await tokenEscrow.claimVaultArweaveIx({
392
+ * depositor, assetId, claimant, claimantTokenAccount,
393
+ * escrowTokenAccount, messageNonce: escrow.nonce,
394
+ * });
395
+ * // Idempotent-create the claimant ATA if it's the canonical derivation.
396
+ * await sendTx([createAtaIx?, ed25519Ix, claimIx]);
397
+ * ```
398
+ */
399
+ claimVaultArweaveIx(args: {
400
+ depositor: Address;
401
+ assetId: Uint8Array;
402
+ claimant: Address;
403
+ claimantTokenAccount: Address;
404
+ escrowTokenAccount: Address;
405
+ /** 32-byte nonce from the on-chain EscrowToken PDA (`escrow.nonce`). */
406
+ messageNonce: Uint8Array;
407
+ }): Promise<Instruction>;
339
408
  /**
340
409
  * Submit an Ethereum ECDSA signature to release escrowed vault tokens. See
341
410
  * {@link claimVaultArweave} — same lock semantics: vaults are only claimable
@@ -51,7 +51,7 @@ export type { SolanaANTRegistryConfig } from './ant-registry-readable.js';
51
51
  export { SolanaANTRegistryWriteable } from './ant-registry-writeable.js';
52
52
  export type { SolanaANTRegistryWriteableConfig } from './ant-registry-writeable.js';
53
53
  export type { AclMaintenanceOp, AclMaintenanceRole, } from '../types/ant-registry.js';
54
- export { ANTEscrow, TokenEscrow } from './escrow.js';
54
+ export { ANTEscrow, TokenEscrow, assertVaultClaimable, isVaultClaimable, CLOCK_SKEW_TOLERANCE_SECONDS, } from './escrow.js';
55
55
  export type { ANTEscrowConfig, EscrowAntState, EscrowAssetType, EscrowProtocol, EscrowTokenState, } from './escrow.js';
56
56
  export { canonicalMessage, canonicalMessageV2, bytesToHexLower, } from './canonical-message.js';
57
57
  export type { CanonicalMessageInput, CanonicalMessageV2Input, EscrowNetwork, } from './canonical-message.js';
@@ -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.17";
16
+ export declare const version = "4.0.0-solana.19";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar.io/sdk",
3
- "version": "4.0.0-solana.18",
3
+ "version": "4.0.0-solana.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ar-io/ar-io-sdk.git"