@dexterai/vault

中文文档
0.44.4发布于 2026/11/07MITTypeScriptESM + CJS近 30 天下载 4598

Canonical off-chain mirror of the dexter-vault Solana Anchor program — Solana instruction builders, byte-precise message encoders, account decoders, secp256r1/Ed25519 precompile helpers, counterfactual Swig derivation, and signer interfaces. The single so

原始使用文档
文档目录21 节
  1. Why this package exists
  2. Install
  3. The byte contract
  4. Provision a vault (server-side enrollment)
  5. Settle a tab voucher (facilitator-side)
  6. Read vault state
  7. Read crystallized claims (the reservation tier)
  8. Derive the counterfactual Swig address
  9. Sessions: one tab per counterparty
  10. Credit primitives
  11. Spend grants ( @dexterai/vault/grant )
  12. Approval and refusal evidence ( @dexterai/vault/mandate )
  13. Byte-parity guarantee
  14. Architecture
  15. Subpath exports
  16. One-ceremony live session rotation (unpublished 0.42.1 repa…
  17. Signer abstraction
  18. Versioning
  19. Publishing
  20. License
  21. Local staging when GitHub runners are unavailable

Dexter

@dexterai/vault

The off-chain mirror of the dexter-vault Anchor program. Byte-precise instruction builders, message encoders, account decoders, and signer abstractions for the passkey-rooted vault.

npm Node Vault program

Building an app? Reach for @dexterai/x402 instead: it gives your agent a spending limit in a few lines, both the buyer and seller sides, and depends on this package transitively. Come to @dexterai/vault when you assemble your own vault transactions: a facilitator, a custom settlement path, or a second Open Tabs Standard implementation. This is the engine, not the front door.


Why this package exists

Three places used to hand-roll the same protocol: dexter-api/src/vault/, dexter-facilitator/src/vault/, and dexter-vault/tests/. One of them registered role 3 (ProgramExec for settle_tab_voucher); two did not. The end-to-end tab-settle smoke kept failing with Role not found for ID: 3, and it ate hours of debugging on 2026-06-02 before anyone caught the drift.

This package is the structural fix. The canonical 4-role Swig provisioner, every instruction builder, every byte-precise message encoder, the vault account decoder, and the precompile helpers each live in exactly one file. Every consumer (dexter-api, dexter-facilitator, dexter-vault tests, @dexterai/x402/tab) imports from here. The drift bug class is gone.

If you are about to hand-roll a vault instruction builder, a precompile message, a Swig role list, or a vault account decoder, import from here instead.


Install

npm install @dexterai/vault

Targets the dexter-vault V7 program: 70 generated-IDL instructions, per-counterparty SessionAccount PDAs, and a monotonic per-vault PasskeyAuthorization replay guard. Pre-V7 vaults must complete the program's migration path before using V7 passkey-gated builders; V5 vaults migrate through V6 first.

The byte contract

Every byte the on-chain program checks lives here, in exactly one file each: instruction discriminators, the 188-byte V2 session-registration message, the 252-byte V3 revocation message, the 44-byte voucher payload, the canonical 200-byte passkey-authorization envelope, and the Vault + SessionAccount + PasskeyAuthorization account layouts. The dexter-vault program is the source of truth; this package is the TypeScript that talks to it, with byte-parity locked by snapshot tests (see below). A programmatic Swig role makes the non-custodial spend path possible, and the entire spend path goes through the vault program, with no master key, no escrow, and no trust.


Provision a vault (server-side enrollment)

import {
  buildSetSwigAtomicFromIdentity,
  SWIG_BIRTH_MODE_OWNER_ONLY,
} from '@dexterai/vault/instructions';
import { Transaction } from '@solana/web3.js';

// `clientDataJSON` and `authenticatorData` come from the owner passkey ceremony
// over buildSetSwigOperationMessage(expectedSwigAddress).
const atomicBirth = buildSetSwigAtomicFromIdentity({
  vaultPda,
  feePayer: feePayerKeypair.publicKey,
  dexterMasterPubkey: sessionMaster.publicKey,
  identitySeed: userHandleBytes,           // per-user, stable, ≤32 bytes
  hmacKey: serverSecret.subarray(0, 32),   // 32-byte HMAC key
  clientDataJSON,
  authenticatorData,
  spendLimitAtomic: 0n,
  sessionTtlSeconds: 0n,
  spendMode: SWIG_BIRTH_MODE_OWNER_ONLY,
});

const tx = new Transaction().add(p256VerifyInstruction, atomicBirth);

Fresh enrollment is one Vault instruction so Swig creation, the program-defined authority spine, role-0 neutering, and the reverse SwigVaultBinding commit or roll back together. The legacy buildSwigCreationBundle multi-instruction API is retained only as a fail-closed compatibility symbol and always throws.

Settle a tab voucher (facilitator-side)

import { buildSettleTabVoucherInstruction } from '@dexterai/vault/instructions';
import { buildEd25519VerifyInstruction } from '@dexterai/vault/precompile';
import { buildVoucherMessage } from '@dexterai/vault/messages';
import { readVaultFull } from '@dexterai/vault/reader';

const vaultState = await readVaultFull(connection, vaultPda);
const voucherMessage = buildVoucherMessage(channelId, cumulativeAmount, sequenceNumber);

const tx = new Transaction().add(
  // Ed25519 precompile verifies the session key signed the voucher bytes.
  buildEd25519VerifyInstruction(sessionPubkey, sessionSignature, voucherMessage),
  // settle_tab_voucher consumes the verified voucher; Swig role 3 drives the SPL transfer.
  // allowedCounterparty names the per-counterparty session PDA being settled against.
  buildSettleTabVoucherInstruction({
    vaultPda,
    swigAddress: new PublicKey(vaultState.swigAddress!),
    dexterAuthority: sessionMaster.publicKey,
    allowedCounterparty,
    channelId,
    cumulativeAmount,
    sequenceNumber,
  }),
);

Read vault state

import { readVaultOnchain, readVaultFull } from '@dexterai/vault/reader';
import { fetchSessionAccount, isSessionLive } from '@dexterai/vault/session';

// Slim shape: { exists, pendingVoucherCount, pendingWithdrawal }.
const slim = await readVaultOnchain(connection, vaultPda);

// Full shape adds: { version, swigAddress, dexterAuthority, liveSessionCount }.
const full = await readVaultFull(connection, vaultPda);

// V7: per-counterparty session state remains in its own PDA, not in the vault.
const s = await fetchSessionAccount(connection, vaultPda, allowedCounterparty);
if (s && isSessionLive(s)) {
  console.log(`tab open: ${s.session.spent} / ${s.session.maxAmount}`);
}

Read crystallized claims (the reservation tier)

A voucher can be crystallized into a LockedClaim: an irreversible, buyer-unwithdrawable reservation of the spent amount. The vault tracks the running sum in outstanding_locked_amount, surfaced by readVaultFull as outstandingLockedAmount. This is the reservation that backs lock-mode tabs — the standard tab protection as shipped, surgically reserving only the accrued amount rather than freezing the whole wallet.

import { readVaultFull, fetchVaultLockedClaims, decodeLockedClaim } from '@dexterai/vault/reader';

// Vault-level total: the sum the withdrawal gate reserves out of the balance.
const { outstandingLockedAmount } = await readVaultFull(connection, vaultPda);

// Per-claim detail. Each claim is a terminal state machine: Pending → Settled
// or Pending → Abandoned. Filter to the live (unsettled) reservations.
const pending = await fetchVaultLockedClaims(connection, vaultPda, { status: 'Pending' });
// reconciliation invariant: sum(pending.amount) === outstandingLockedAmount
for (const c of pending) {
  console.log(`${c.voucherHash}: ${c.amount} held by ${c.currentHolder} (${c.status})`);
}

// decodeLockedClaim(address, accountData) decodes a single account you already
// hold — the same moving-cursor decoder fetchVaultLockedClaims uses internally.

Derive the counterfactual Swig address

import { deriveCounterfactualAddresses } from '@dexterai/vault/counterfactual';

// Returns both the state PDA (program-owned) and the wallet-address PDA
// (system-owned, the asset holder). Useful for showing a deposit address
// before the Swig is on chain.
const { swigStateAddress, swigWalletAddress } = await deriveCounterfactualAddresses({
  identitySeed: userHandleBytes,
  hmacKey: serverSecret.subarray(0, 32),
});

Sessions: one tab per counterparty

Each session lives in its own SessionAccount PDA, [b"session", vault, allowed_counterparty]: one tab per (vault, counterparty), many counterparties per vault. Registering against a counterparty that already has a session replaces it in place (same seed) and resets the meters (spent, currentOutstanding); anything building UX on top should warn before replacing.

Registering requires the sibling contract: the program checks that the transaction names every other version≠0 session of the vault, so it can sweep expired ones and prove the new cap does not overcommit the vault's balance.

import { buildRegisterSessionKeyInstruction, buildRevokeSessionKeyInstruction } from '@dexterai/vault/instructions';
import { fetchVaultSessionAccounts, sessionPdasOf, waitForSession, resolveVaultUsdcAta } from '@dexterai/vault/session';

// fetch siblings FRESH immediately before building (the gate sweeps expired
// siblings; a stale list fails the on-chain completeness check)
const siblings = sessionPdasOf(await fetchVaultSessionAccounts(connection, vaultPda));
// resolve the vault's USDC ATA — returns its address if it exists, or `null`
// for a credit-only vault that never received a deposit (see note below)
const vaultUsdcAta = await resolveVaultUsdcAta(connection, swigAddress);
const ix = buildRegisterSessionKeyInstruction({ ...args, payer, vaultUsdcAta, siblingSessionPdas: siblings });
// ... send [secp256r1Precompile, ix] ...
await waitForSession(connection, vaultPda, allowedCounterparty, { expectedSessionPubkey });

The builder handles the fiddly parts of the sibling list (excludes the target, dedups, sorts strict-ascending by raw bytes, marks all writable); your only job is fetching it fresh. waitForSession is content-aware confirm-visibility: it waits for the new session_pubkey, because on a replace the old registration also passes existence and version checks under read-your-writes lag.

vaultUsdcAta is optional. The register gate computes backing as own_USDC + available_standby_credit. A self-funded vault passes its USDC ATA so its balance counts; a credit-only vault never receives a deposit, so it has no ATA — pass vaultUsdcAta: null and own-USDC is counted as 0 (backing is the standby line alone). resolveVaultUsdcAta makes this decision for you (derive-and-probe): use its PublicKey | null result directly. Omitting an ATA that exists only ever understates backing, so it is safe; a self-funded vault that wants its balance counted must pass it.

Credit primitives

The vault program supports a tab that spends past the user's balance, backed by a financier's standby capital, structured so the buyer cannot rug the financier and the financier cannot seize more than the agreed bound. The builders for that path live here:

import {
  drawCredit,
  originateLineCredit,
  repayCredit,
  seizeCollateral,
} from '@dexterai/vault/tab';

Credit is not a separate product. It is a tab that can spend past its balance, so it composes the same primitives: open_standby arms the backing, draw_credit / repay_credit move the borrowed balance, seize_collateral runs the default path, and the LockedClaim crystallized tier (@dexterai/vault/instructions) plus factoring / instant payout (@dexterai/vault/factoring) build on top. Demonstrated on Solana mainnet: a draw, a repayment, and a default-and-seize. This is the newest surface in the package; treat it accordingly.

originateLineCredit is the evidence-bearing draw path. It derives the versioned evidence and terms commitments, then returns (but does not send) one atomic record_line_origination -> draw_credit -> Swig SignV2 envelope. The per-draw receipt is immutable evidence for the existing PrincipalNode facility; it is not a transferable claim and is not a second balance ledger. Read it with readLineOrigination or decode the compact event and call fetchLineOriginationForEvent for the required full receipt.

New first draws should commit LineTermsV2. V2 binds the agreed due date, requested recovery window, rate, principal, asset, and policy/evidence hashes, but deliberately does not contain defaultAt or curePeriodSeconds: those values are finalized from the actual landing clock. Existing V1 exports remain unchanged.

import { originateLineCredit } from '@dexterai/vault/tab';
import { LINE_TERMS_V2_SCHEDULE_POLICY } from '@dexterai/vault/credit';

const result = await originateLineCredit({
  ...draw,
  terms: {
    schedulePolicy: LINE_TERMS_V2_SCHEDULE_POLICY,
    assetDecimals: 6,
    dueAt,
    legalAgreementHash,
    underwritingHash,
    policyHash,
    pricingHash,
  },
});
// originateLineCredit binds the exact live mint, principal, node rate, and
// requested recovery window before hashing the V2 envelope.

Spend grants (@dexterai/vault/grant)

An app proposes a bounded spend-tab; the user's passkey endorses the exact 188-byte registration scope. Two halves:

// App side: produce a self-contained request blob (no keys, signs nothing):
import { requestSpendGrant, encodeSpendGrantRequest } from '@dexterai/vault/grant';

const blob = requestSpendGrant({
  app: { name: 'Acme Research', domain: 'acme.example' },
  counterparty: SELLER_ADDRESS,        // the on-chain binding (session PDA seed)
  capAtomic: '5000000',                // $5; the user may only SHORTEN
  expiresAtUnix: Math.floor(Date.now() / 1000) + 7 * 86400,
  sessionPubkey: AGENT_SESSION_PUBKEY, // optional; see custody note
});
const consentUrl = `https://dexter.cash/grant?req=${encodeSpendGrantRequest(blob)}`;

// Consent side: parse untrusted input, apply shorten-only edits, run the
// passkey ceremony over the exact bytes, end at the SIGNED GRANT:
import { parseSpendGrantRequest, approveSpendGrant } from '@dexterai/vault/grant';

const request = parseSpendGrantRequest(rawBlob);
const approved = await approveSpendGrant({
  request,
  vaultPda,                            // the USER's vault, never from the blob
  edits: { capAtomic: '2000000' },     // shorten-only; raises throw
  sign: (message) => myWebAuthnPipeline(message),
});

An optional seller field carries { calledUrl, offer?: { format: 'jws', signature }, expectedHost? }. calledUrl must be HTTPS with no credentials or explicit port and at most 2048 characters. The compact JWS has three base64url segments and at most 4096 characters; expectedHost is a lowercase ASCII hostname of at most 253 characters. Both request entry points reject malformed seller input with bad_seller and drop unknown keys. The request blob stays unsigned. The quote inside carries the store's signature; callers verify that signature and the seller binding separately.

If the blob carries sessionPubkey, the requesting app's agent holds the session secret and can drive spend to the consented cap on its own pacing. Omit it and approveSpendGrant generates the keypair caller-side, so the requester never sees the secret. Either way exposure is bounded by cap × counterparty × expiry, enforced on-chain at settle.


Approval and refusal evidence (@dexterai/vault/mandate)

verifyMandateApproval returns ceremonyNonce as lowercase hex only after the complete 200-byte approval verifies. It returns null for a legacy 32-byte challenge or an invalid or unavailable approval.

verifyCaseFailedTransaction accepts three CaseRefusalAttempt variants: settle_voucher, settle_tab_voucher and settle_locked_voucher. The existing variants retain their counterparty fields. A locked payout uses:

const attempt = {
  instruction: 'settle_locked_voucher' as const,
  vault, claim, destinationOwner,
  amount: 1_000_000n, // Expected amount from the claim evidence.
  holder,           // Optional expected holder signer.
  authority,        // Optional expected Dexter authority signer.
};
verifyCaseFailedTransaction(transaction, signature, {
  program: DEXTER_VAULT_PROGRAM_ID.toBase58(), code: 6070, attempt,
});

The locked payout check decodes the following Swig SignV2 TransferChecked and requires the canonical source USDC ATA, USDC mint, expected destination ATA, claim amount and six decimals. Bound accounts must be signed static keys. The caller supplies the expected claim amount and establishes the claim state and vault's Swig identity from separate evidence. Transaction signatures cover the attempted instructions; the RPC node reports the failure code and logs.

Byte-parity guarantee

tests/byte-parity.test.ts, tests/precompile.test.ts, tests/swigBundle.test.ts, tests/counterfactual.test.ts, and tests/reader.test.ts together snapshot:

  • Every hand-pinned instruction discriminator in DISCRIMINATORS, derived from sha256("global:<name>") and checked against the generated IDL.
  • The core session/voucher message layouts, byte-by-byte: 188-byte V2 session registration, 252-byte V3 exact-state revocation, and the 44-byte voucher payload, plus operation-specific authorization-message suites.
  • Both precompile builders, secp256r1 (SIMD-0075) and Ed25519, including the 14-byte offsets table.
  • The vault account decoder for the V7 layout, the 162-byte SessionAccount decoder, and the per-vault replay-guard decoder.
  • The atomic Swig-birth contract: deterministic identity derivation, canonical reverse binding, owner-only mode encoding, exact 211-byte consent, semantic ProgramExec marker catalog, and fail-closed legacy enrollment.
  • The counterfactual derivation for a known seed.

If a future change drifts any of these by a single byte, the snapshot tests fail. The on-chain dexter-vault program is the ultimate referee; these tests catch the drift before it ships.

npm test

Pre-audit, and we say so. The dexter-vault program this package mirrors is not yet externally audited; funding is in flight. The report and any findings publish in the program repo. Responsible disclosure: [email protected].

Architecture

                ┌─────────────────────────────────┐
                │  dexter-vault (Anchor program)  │  ← source of truth
                │  V7: 70 IDL instructions + guard│
                └────────────────┬────────────────┘
                                 │ defines bytes
                                 ▼
                ┌─────────────────────────────────┐
                │  @dexterai/vault (this package) │  ← off-chain mirror
                │  byte-parity locked by tests    │
                └────────┬────┬──────────┬────────┘
                         │    │          │
       ┌─────────────────┘    │          └──────────────────┐
       ▼                      ▼                             ▼
┌──────────────┐     ┌──────────────────┐         ┌───────────────────┐
│ dexter-api   │     │ dexter-facilitator│         │ dexter-vault tests│
│ (DB + glue)  │     │ (tab settle path) │         │ (anchor smokes)   │
└──────────────┘     └──────────────────┘         └───────────────────┘

        ▲                                                     ▲
        │                                                     │
        └────────── @dexterai/x402/tab imports message ───────┘
                    helpers from @dexterai/vault; keeps
                    the HTTP wrapping

Subpath exports

Each subpath is a tree-shakeable entry point. Pull only what you need.

SubpathContents
@dexterai/vaultRe-exports types + counterfactual + session for convenience
@dexterai/vault/typesVaultState, VaultStateFull, SessionAccountState, SignedVoucher, VoucherPayload, AtomicAmount, HumanAmount, TabNetworkId, and the rest
@dexterai/vault/constantsProgram IDs, mint IDs, the complete pinned DISCRIMINATORS map, PDA seeds, and OTS domain tags
@dexterai/vault/instructionsEvery builder, including replay-guard initialization, atomic Swig birth, governed Send/swap and trade-grant flows, exact cash-backed Native Tab open, session register/revoke and one-ceremony live replacement, Tab settlement, withdrawals, passkey rotation/proof, and migrations. Legacy multi-instruction Swig enrollment and unsigned Tab close are fail-closed.
@dexterai/vault/messagessessionRegisterMessage (188 bytes), sessionRevokeMessage (252 bytes), sessionReplaceV1Message (348 bytes), buildLegacyV1TabBindingOperationMessage (501 bytes), buildVoucherMessage (44 bytes), and the operation-specific consent builders
@dexterai/vault/readerreadVaultOnchain, readVaultFull, passkey-safe Vault decoding, LockedClaim readers, and Line receipt/event fetchers (decodeLineOrigination, readLineOrigination, fetchLineOriginationForEvent)
@dexterai/vault/sessionV7 per-counterparty sessions: deriveSessionPda, fetchSessionAccount, fetchVaultSessionAccounts, sessionPdasOf, waitForSession, registerSessionWithRetry, resolveVaultUsdcAta (derive-and-probe the vault USDC ATA, null for credit-only), and the rest
@dexterai/vault/grantSpend-grant consent flow: requestSpendGrant, parseSpendGrantRequest, approveSpendGrant, encode/decode
@dexterai/vault/precompilebuildSecp256r1VerifyInstruction, buildPrecompileMessage, buildEd25519VerifyInstruction
@dexterai/vault/counterfactualderiveCounterfactualAddresses
@dexterai/vault/signers · /node · /browserEd25519Signer / PasskeySigner interfaces; NodeEd25519Signer; WebAuthnAssertion (browser P-256 ceremony)
@dexterai/vault/tabComposed verbs: openTab, settleTab, readTabMeter, drawCredit, originateLineCredit, repayCredit, seizeCollateral
@dexterai/vault/factoringcomputeFactoringSplit, buildInstantPayoutInstructions
@dexterai/vault/kitkitInstructionsToWeb3, getRpc (Swig-kit↔web3 bridge)

The legacy direct @dexterai/vault/connect ceremony was retired with the V7 authorization protocol. It cannot satisfy the canonical replay-guard envelope from a third-party WebAuthn origin. Website integrations must use @dexterai/connect, whose hosted Dexter consent window pins the signing origin while leaving the opener website unrestricted.

Existing V7 vaults must run the permissionless, idempotent buildInitializePasskeyAuthorizationInstruction once before their first guarded operation. A vault-version migration alone does not create this replay-guard PDA.

One-ceremony live session rotation (unpublished 0.42.1 repair)

V7 rejects direct overwrite of an unexpired SessionAccount. The previously safe rotation shape requires two guard-bound WebAuthn assertions in one transaction: revoke_session_key(N) followed by register_session_key(N+1). With canonical 200-byte authorization challenges, that exact transaction is 1,621 bytes at zero sibling sessions even after every eligible account is compressed through an address lookup table; Solana's packet cap is 1,232 bytes.

replace_session_key_v1 repairs that transport dead-end with one assertion. Its fixed 348-byte operation binds the current guard nonce, canonical target PDA, all ten retired registration fields/meters, and all six replacement fields. The instruction preserves the meters, requires the replacement limits to cover them, and changes only the passkey-endorsed session scope. No retired fields are duplicated in instruction data: the program rebuilds them from the live target, so any post-ceremony meter drift makes the operation hash stale.

composeRevokeThenRegister now emits [secp(replace), replace_session_key_v1] for a live target and retains [secp(register), register_session_key] for absent, cleared, or expired targets. Before every replace attempt it re-fetches the target, replay guard, and complete sibling set; target-meter or guard-nonce drift fails closed before retry. The deprecated revokeCeremony input is ignored and never enters a transaction.

A full address lookup table is required for realistic browser payloads. Pinned wire sizes for zero/one/four sibling sessions are respectively 991/993/999 bytes with the full ALT. The same legacy shapes are 1234/1267/1366 bytes and v0 without the ALT is 1236/1269/1368 bytes, so neither uncompressed form is a safe fallback under Solana's 1,232-byte cap. These are PREP artifact measurements; they are not evidence of package publication or program deployment.


Signer abstraction

interface Ed25519Signer {
  readonly publicKey: Uint8Array;                              // 32 bytes
  sign(message: Uint8Array): Promise<Uint8Array>;              // 64-byte signature
}

interface PasskeySigner {
  readonly credentialId: Uint8Array;
  sign(challenge: Uint8Array): Promise<{
    signature: Uint8Array;
    clientDataJSON: Uint8Array;
    authenticatorData: Uint8Array;
  }>;
}

NodeEd25519Signer ships at @dexterai/vault/signers/node. The browser passkey signer is WebAuthnAssertion at @dexterai/vault/signers/browser: a pure-browser P-256 ceremony that runs navigator.credentials.get() and returns the three on-chain-ready buffers (64-byte compact lowS signature, raw clientDataJSON, raw authenticatorData), with zero fetch calls. It implements PasskeySigner; consumers compose it with their own server-policy adapter.

Versioning

The current SDK targets the dexter-vault V7 program (70 generated-IDL instructions; per-counterparty SessionAccount PDAs; a monotonic per-vault PasskeyAuthorization replay guard; role 3 ProgramExec for settle_tab_voucher on every new Swig). Pre-V7 vaults must complete the program's migration path before using V7 passkey-gated builders; V5 vaults migrate through V6 first. The crystallized-claim reader (outstandingLockedAmount, fetchVaultLockedClaims, decodeLockedClaim) and the settle_locked_voucher Swig marker arrived in 0.10.0. Future program versions bump the SDK major or document the delta in the CHANGELOG. The byte-parity tests are the structural lock: any layout change requires an explicit snapshot update.

Publishing

The former local release* npm scripts have been removed. Use the verified archive and maintainer approval procedure below for publication.

Releases use Publish Vault SDK. The build job runs npm run verify:release:package, which tests the committed source, packs it, and checks fresh ESM, CommonJS, and TypeScript consumers. Its tarball and release-proof.json are saved as a GitHub Actions artifact. A second job uploads the same tarball to npm staging for a maintainer to approve with 2FA.

Configure the vault-npm-production GitHub environment with a deployment rule allowing release tags. In the npm settings for @dexterai/vault, add a GitHub Actions trusted publisher with these exact values:

SettingValue
OrganizationDexter-DAO
Repositorydexter-vault-sdk
Workflow filenamepublish-vault.yml
Environmentvault-npm-production
Allowed actionEnable npm stage publish only

These settings require configuration in GitHub and npm. Adding the workflow to the repository does not configure either service. The workflow uses Node 24 and npm 11.19.1, which meet the requirements for npm trusted publishing. Vault's source repository is private, so the publish job disables provenance generation as required by npm's current support for private repositories.

To release a stable version, merge the version and lockfile changes into main, then push a tag matching package.json, such as v0.43.4. The workflow checks that the tagged commit belongs to main. Pull requests run package verification without requesting publication approval.

The staging job checks the saved hashes and submits the tested tarball using OIDC. Its run summary shows the stage ID and an npm stage approve command; the vault-stage artifact contains the npm response and package checksums. Review that package, then approve it in npm with 2FA. Until that approval, the release remains pending and cannot be installed from the public registry. The approval happens in npm because the GitHub plan for this private repository does not support required reviewers.

After approval, rerun the workflow's staging job to verify npm's package integrity and the latest tag. A published version is accepted only when its bytes match the tested package. If a staging attempt was interrupted, inspect npm's Staged Packages tab before rerunning: a pending stage already reserves that package version.

For a manual run, choose the release tag in GitHub Actions or use:

gh workflow run publish-vault.yml --repo Dexter-DAO/dexter-vault-sdk --ref v0.43.4

License

MIT. © 2026 Dexter.


Part of the Dexter-DAO open source family.

Local staging when GitHub runners are unavailable

Use Node 22.14 or newer and npm 11.19.1. Log in with npm login --auth-type=web, then run npm run verify:release:package from a clean, committed checkout. The selected commit must be merged into main before publication.

Stage the exact archive recorded in the verifier's release-proof.json:

npm stage publish /path/from/release-proof/packed/dexterai-vault-VERSION.tgz \
  --ignore-scripts --access public --tag latest --provenance=false --json
npm stage view STAGE_ID --json

Compare the staged integrity and shasum with the package proof. Wait until npm stage view reports staged before running npm stage approve STAGE_ID; this avoids requesting approval while npm's automated review is still running. Complete the browser authentication, then verify that npm's version, integrity and latest tag match the tested archive. Retain the proof and stage receipt.

版本与发布记录

@dexterai/vault 共发布 69 个版本,首次发布于 2026/06/04;过去 12 个月发布了 69 个版本。

版本发布日期距今
0.44.4latest2026/11/075 天前
0.44.32026/10/3013 天前
0.44.22026/10/3013 天前
0.44.12026/10/2913 天前
0.44.02026/10/2914 天前
0.43.92026/10/2419 天前

其他发布通道:next → 0.43.3-rc.1。

关于 @dexterai/vault 的常见问题

@dexterai/vault 是什么?

Canonical off-chain mirror of the dexter-vault Solana Anchor program — Solana instruction builders, byte-precise message encoders, account decoders, secp256r1/Ed25519 precompile helpers, counterfactual Swig derivation, and signer interfaces. The single so

如何安装 @dexterai/vault?

在项目目录运行 npm install @dexterai/vault,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 dexter-mandate,可以用 npx dexter-mandate 直接运行。

@dexterai/vault 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dexterai/vault 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dexterai/vault 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dexterai/vault 可以商用吗?

@dexterai/vault 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dexterai/vault 的下载量有多少?

根据 npm 官方下载统计,@dexterai/vault 在 2026/10/12 – 2026/11/10 这 30 天内被下载 4,598 次,日均约 153 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dexterai/vault 还在维护吗?

最新版本 0.44.4 发布于 2026/11/07(5 天前);至今共发布 69 个版本,其中过去 12 个月发布了 69 个;首次发布于 2026/06/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。