@cfxlabsinc/b2b-services
    Preparing search index...

    Module @cfxlabsinc/mintfx-program

    @cfxlabsinc/mintfx-program

    The mintfx Anchor program (programs/mintfx), its TypeScript client (src/, imported by subpath: ., ./generated, ./pda, ./uuid, ./events, ./token, ./state, ./search), and its test harness (./test, which deploys the pinned .so in src/test-harness/fixtures).

    src/generated/ is written by codama from the program's IDL when the program builds, so regenerate it with nx run mintfx-program:build-program rather than editing it. It is checked in, and the check-generated-client target (part of test) fails when it differs from a fresh generation, so commit it with the program change. It holds every instruction builder, so callers import those from ./generated. What is hand-written on top is PDA derivation, account decoders and search filters, the event decoders, and the Token Metadata update. It makes no RPC calls.

    Task Command
    Build the program nx run mintfx-program:build-program
    Every suite nx run mintfx-program:test
    Unit tests nx run mintfx-program:test-unit
    Client, pinned .so nx run mintfx-program:test-client
    Anchor suites nx run mintfx-program:test-anchor (-c <suite> for one)
    Cargo tests nx run mintfx-program:test-cargo
    Clippy / rustfmt nx run mintfx-program:clippy, rustfmt
    Refresh test fixtures nx run mintfx-program:update-test-fixtures
    Verifiable build nx run mintfx-program:build-verifiable -c dev|prod
    Typecheck nx run mintfx-program:typecheck

    Import the builders from @cfxlabsinc/mintfx-program/generated and pass { programAddress }. It is required: the program id is chosen per environment when the program is built, so nothing in the client bakes one in. MINTFX_ADDRESSES has the three.

    adminAuthority is required for the same reason, on the ten builders that take it (createAuthority, updateAuthority, destroyAuthority, createToken, createToken2022, createMetadata, the three create*EscrowForToken, and reclaimExcessLamports). It used to be optional, defaulting to the address Anchor wrote into the IDL from #[account(address = ADMIN_AUTHORITY)] — but that address is env!("MINTFX_ADMIN_AUTHORITY_ADDRESS"), resolved at build time, and this package is generated from a localnet build and shared by all three environments. So the default was the localnet authority on dev and mainnet, and a caller who omitted it got a meta that is neither a signer nor the right key: a failure at signing, or ConstraintAddress on chain. codama.mjs drops the default (scripts/require-admin-authority.mjs), so omitting it is now a compile error rather than a wrong key.

    import { MINTFX_ADDRESSES } from "@cfxlabsinc/mintfx-program";
    import { getMintInstructionAsync } from "@cfxlabsinc/mintfx-program/generated";
    import { deterministicUUIDBytes } from "@cfxlabsinc/mintfx-program/uuid";

    const programAddress = MINTFX_ADDRESSES.mainnet;
    const instruction = await getMintInstructionAsync(
    {
    authority,
    payer,
    tokenId,
    ik: deterministicUUIDBytes(ik),
    targetAmounts,
    targetAccountsStartIdx: 0,
    },
    { programAddress },
    );

    The builders derive every account they can, so you pass the signers and the arguments. What they cannot know is left to the caller:

    • Token accounts: the account a wallet is debited from (ledgerAccount on redeem and withdraw, sourceLedgerAccount on transfer, sourceTokenAccount on redeem_external and withdraw_external) is an input. The program takes any token account of the mint that the wallet owns, so nothing assumes the associated one. getLedgerAccountATAAddress from ./pda derives the conventional one.

    • Ids: ik and every wallet id are 16 bytes. Convert a uuid with deterministicUUIDBytes from ./uuid.

    • Amounts: integers in base units, so scale by 10 ** decimals yourself.

    • Remaining accounts: append them after the builder's accounts, in the order of the amounts, with a start index of 0 unless you put your own accounts first.

      • mint, commit_withdrawal: the target token accounts, writable.
      • commit_redeem: the fee token accounts, writable.
      • transfer: each target wallet's authority PDA, readonly, then each target's token account, writable. The token account must be of the mint and owned by that target's authority PDA; it need not be the associated one. The bumps argument is the authorities' bumps, from getLedgerAccountAuthorityWithBump in ./pda. Bumps for the associated token accounts may follow them, as older clients sent, but the program no longer reads them.
    • Permissions: create_authority takes every flag. Spread your permissions over DEFAULT_EMPTY_PERMISSIONS from ./state.

    • Refund destination: void_withdrawal and void_redeem refund to the token account you pass as refundDestination; nothing derives it, because where a refund goes is a decision. For a ledger wallet's own account, derive it with getLedgerAccountATAAddress from ./pda and pass the wallet id as refundWalletId, which the program checks against the destination's owner. For any other destination pass refundWalletId: null.

    packages/mintfx-program/tests/instructions.ts builds instructions this way; copy from it.

    The client is stateless. It builds instructions, derives PDAs and decodes bytes, and never calls an RPC endpoint, so the caller decides how, when and how often to read.

    • One account: derive the address (./pda), fetch it with the generated fetchers (./generated, for example fetchRedemptionInfo), then shape it with the pure parsers in ./state (parseRedemptionInfoAccountData, parseWithdrawalInfoAccountData, parseAuthorityAccountData).
    • Every account of a kind: getProgramAccounts with getAccountKindFilter(kind) from ./search, then decodeAccountOfKind. Use getAccountKind to name an account you fetched without knowing its kind.
    • Events: fetch the transaction yourself and pass it to parseCPIEvents (./events). getIdempotentActionAddress names the account that records an action, and the history of transactions that touched it is where its events live.
    • Token metadata: getMintMetadataAddress (./pda) and decodeTokenMetadataAccount (./token).

    packages/mintfx-program/tests/reads.ts implements all of that, with the names and signatures the client used to export. Copy it into your own code.

    The generated get*InstructionAsync builders derive every PDA account, plus eventAuthority and program, so a caller passes the arguments (tokenId, ik, ...) and the signers, never the addresses. Two things in codama.mjs make that possible:

    • The IDL lists every seed, but @codama/nodes-from-anchor skips any seed whose path is nested, and ours are all args.token_id. So each instruction's single args struct is first flattened into top-level arguments (scripts/flatten-idl-args.mjs). Borsh writes a struct as its fields back to back, so the instruction data is byte for byte the same.
    • The IDL leaves the #[event_cpi] accounts as bare names, so their defaults are declared there: the PDA of the constant seed __event_authority, and the program's own id.

    pda.ts exports friendly names for reading an address without building an instruction: each hashes string ids with deterministicUUIDBytes and calls the generated find*Pda finder, so there is one derivation per PDA. Codama names colliding PDAs after the first instruction that uses them, so codama.mjs (canonicalizePdas) merges the duplicates and gives each finder a stable name. tests/client/pdaParity.spec.ts pins every address to a known-good value.

    The hand-written instruction helpers are gone. They were thin mappers over the generated builders (a uuid to bytes, an amount scaled, some remaining accounts), so callers use the builders directly; see "Building instructions".

    Entry point Removed Use instead
    ./mint createMintInstruction getMintInstructionAsync
    ./transfer createTransferInstruction getTransferInstructionAsync
    ./redeem createRedeemInstruction, createCommitRedeemInstruction, createVoidRedeemInstruction getRedeemInstructionAsync, getCommitRedeemInstructionAsync, getVoidRedeemInstructionAsync
    ./withdraw createWithdrawInstruction, createCommitWithdrawalInstruction, createVoidWithdrawalInstruction getWithdrawInstructionAsync, getCommitWithdrawalInstructionAsync, getVoidWithdrawalInstructionAsync
    ./rbac createAuthorityInstruction, updateAuthorityInstruction, destroyAuthorityInstruction getCreateAuthorityInstructionAsync, getUpdateAuthorityInstructionAsync, getDestroyAuthorityInstructionAsync
    ./token createTokenInstruction, createToken2022Instruction, pauseTokenInstruction, unpauseTokenInstruction getCreateTokenInstructionAsync, getCreateToken2022InstructionAsync, getPauseTokenInstructionAsync, getUnpauseTokenInstructionAsync
    ./token createEscrowInstruction getCreateMintEscrowForTokenInstructionAsync, getCreateRedemptionEscrowForTokenInstructionAsync, getCreateWithdrawalEscrowForTokenInstructionAsync
    ./token createTokenMetadataInstruction getCreateMetadataInstructionAsync. It also fetched the metadata URI in the background, best effort and never awaited; that is gone, so the client does no I/O at all

    The ./mint, ./transfer, ./redeem, ./withdraw and ./rbac entry points no longer exist. ./token keeps updateTokenMetadataInstruction, which builds a Token Metadata program instruction that is not in this program's IDL, and decodeTokenMetadataAccount. The permission types and parsers were already exported from ./state and still are. The DEFAULT_*_START_INDEX constants are gone: the start index is 0.

    • ./pda — ACCESS_CONTROL_SEED, EVENT_AUTHORITY_SEED, MINT_ESCROW_SEED, MINT_IK_SEED, REDEEM_IK_SEED, REDEMPTION_ESCROW_SEED, TOKEN_INFO_SEED, TRANSFER_IK_SEED, WITHDRAW_IK_SEED, WITHDRAWAL_ESCROW_SEED.

    Changed with the program:

    • ./generated — withdraw_external is new. It debits an external wallet that signs the transaction, alongside the operator, instead of a ledger account. It uses the same permission as withdraw, and an IK is the same account either way, so commit_withdrawal and void_withdrawal handle both. It emits WithdrawEvent, as withdraw does.
    • ./generated — void_withdrawal no longer assumes the refund goes back to whoever initiated the withdrawal. Breaking: refundLedgerAccountAuthority and refundLedgerAccountTokenAccount are replaced by one required refundDestination, the token account to credit, and refundWalletId is optional. Passing null is for a destination that is not a ledger wallet's: an external wallet, or an account held for review. It takes VoidWithdrawal whatever the destination, so that permission now decides where escrowed funds go, as CommitWithdrawal does. The IK record does not change and still names the initiator.
    • ./events — VoidWithdrawalEvent has refundWalletId as an option and a new refundDestination: the token account credited, always present. It is the initiator's own account unless the void was directed elsewhere, so compare it with the initiator's before treating a void as a refund to the customer. The previous shape still decodes.
    • ./generated — redeem_external is new, and void_redeem changes exactly as void_withdrawal does: a required refundDestination replaces refundLedgerAccountAuthority and refundLedgerAccountTokenAccount, refundWalletId is optional, and VoidRedeem alone decides where the refund goes. redeem_external debits an external wallet that co-signs with the operator, uses the Redeem permission, and shares the redeem IK with redeem, so commit_redeem and void_redeem handle both. It emits RedeemEvent, as redeem does, and VoidRedeemEvent gains refundDestination and an optional refundWalletId, with the previous shape still decodable.
    • ./events — Breaking: WithdrawExternalEvent and RedeemExternalEvent are gone, and WithdrawEvent / RedeemEvent carry a new sourceAuthority: Option<Address> instead: Some(wallet) from the external instruction, None from the ledger-funded one. MintfxEvent loses the two union members, so a caller that named them — in a list of event names to match, or a cast — has to drop them and read sourceAuthority to tell the two fundings apart. The two names were never emitted on devnet or mainnet, so they get no legacy layout; the pre-sourceAuthority shape of the two base events does, and still decodes with the field names it always had.
    • ./generated — findRefundLedgerAccountAuthorityPda is gone. Neither void instruction derives a refund authority any more.

    Everything the client used to read from chain is gone, along with the instructions the program no longer has.

    Removed because they made RPC calls:

    Entry point Removed Use instead
    ./state getParsedRedemptionInfo, getParsedRedemptionInfoFromAddress, getParsedWithdrawalInfo, getParsedWithdrawalInfoFromAddress, getParsedAuthority, getParsedAuthorityFromAddress, fetchTokenInfoByTokenId reads.ts; the parse*AccountData parsers stay, and tokenIdFromBytes is new
    ./state checkAuthorityPermissions, getParsedRedemptionIk, getParsedRedemptionIkFromAddress, RedemptionIK, parseRedemptionIKAccountData nothing called them
    ./search listAccountsOfKind, listAllProgramAccounts, listAuthorities, listRedemptions getAccountKindFilter, getAccountKind, decodeAccountOfKind
    ./events getCPIEventsFromTransaction, findCompletedIdempotentAction parseCPIEvents and getIdempotentActionAddress
    ./token fetchMaybeTokenMetadata, updateToken2022MetadataInstructions decodeTokenMetadataAccount; for Token-2022, use getUpdateTokenMetadataFieldInstruction from @solana-program/token-2022

    Removed with the program's own changes:

    • ./redeem — createLedgerAccountInstruction. A wallet is a token account its signing PDA owns, conventionally the associated one, so create it with the associated token program.
    • ./state, ./rbac — the canCreateLedgerAccount permission. Bits 6 and 7 are retired and must not be reused. An authority that still holds bit 6 fails every permissioned instruction until the admin rewrites it with update_authority.
    • ./generated — the create_ledger_account and close_idempotent_mint_account instructions. update_authority's newPermissions and UpdateAuthorityEvent.permissions are no longer optional.

    CreateLedgerAccountEvent and CloseIdempotentMintAccountEvent are retired but still decodable, like every other retired event.

    The migrations these served are finished on devnet and mainnet, so 2.4.2 removed them — as a patch, because its commit was scoped to mintfx-program and nx release attributes a scoped commit to the project of that name. 3.0.0 carries the same code under the version the removal always warranted. Treat 2.4.2 as 3.0.0 when reading the history.

    Gone from the public API:

    • ./rbac — recreateAuthorityInstructions, RecreateAuthorityInstructionsInput. getParsedAuthority and getParsedAuthorityFromAddress no longer throw on an account that is not 48 bytes; no such account remains.
    • ./pda — ACCESS_CONTROL_SEED_V1, MINT_SWAP_WALLET_SEED, getMintSwapInputWalletAuthority, getMintSwapInputWalletAddress.
    • ./search — WALLET_AUTHORITY_KINDS. MintfxAccountKind drops ledgerAccount, redemptionWallet and mintSwapWallet, so listAllProgramAccounts reports kind: null for them, and authorities are no longer filtered by data size.
    • ./generated — the builders, parsers, codecs and types for sweep_mint_swap_wallet, close_mint_swap_wallet, close_ledger_account_authority and migrate_redemption_wallet, the MintSwapWallet account helpers, and their event codecs. The MintfxInstruction and MintfxAccount enum values renumber.

    Still decodable: every retired event keeps a hand-written decoder in src/program/legacyEvents.ts, so historical transactions parse unchanged. ReclaimExcessLamportsEvent decodes by name now instead of coming back undecodable. AccountRequiresMigration stays in the error enum as a retired variant, so NotProgramOwned keeps code 6014.

    SolanaNetwork
    MINTFX_ADDRESSES