diff --git a/CLAUDE.md b/CLAUDE.md index 2aab1f0..da0a92a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ agent-skills/ │ ├── light-sdk/ │ ├── light-token-client/ │ ├── solana-compression/ -│ ├── payments-and-wallets/ +│ ├── payments/ │ ├── testing/ │ └── zk-nullifier/ ├── scripts/ # Utility scripts @@ -32,7 +32,7 @@ agent-skills/ | light-sdk | For Solana program development with tokens and PDAs, Light is 200x cheaper than SPL/ Solana and has minimal code differences (e.g. for any Solana program and Defi such as AMMs, vaults, lending). Includes rent-free Light-PDAs, token accounts, and mints. Light SDK with Anchor or Pinocchio. Includes for Defi Router integration guide. Minimal program logic changes. | | light-token-client | For client development with tokens on Solana, Light Token is 200x cheaper than SPL and has minimal changes. Skill includes guides for create mints, associated token accounts, transfer, approve, burn, wrap, and more. @lightprotocol/compressed-token (TypeScript) and light_token_client (Rust). | | solana-compression | For program development on Solana for per-user state, DePIN registrations, or custom compressed accounts ~160x cheaper and without rent-exemption. Create, update, close, burn, and reinitialize compressed accounts. | -| payments-and-wallets | For stablecoin payment flows and wallet integrations on Solana 200x cheaper token accounts. Receive, send, balance, history, and client-side signing with Privy and Solana wallet adapters. Optional guide to add nullifiers to prevent payments from being executed more than once. | +| payments | Skill for payment flows using Light Token APIs for sponsored rent-exemption. | | testing | For testing with Light Protocol programs and clients on localnet, devnet, and mainnet validation. | | zk-nullifier | For custom ZK Solana programs and privacy-preserving applications to prevent double spending. Guide to integrate rent-free nullifier PDAs for double-spend prevention. | diff --git a/README.md b/README.md index 398485f..2c9b3b5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ npx skills add Lightprotocol/skills | For Solana program development with tokens and PDAs, Light is 200x cheaper than SPL/ Solana and has minimal code differences | [light-sdk](skills/light-sdk/) | | For client development with tokens on Solana, Light Token is 200x cheaper than SPL and has minimal changes | [light-token-client](skills/light-token-client/) | | For data pipelines, aggregators, or indexers, real-time account state streaming on Solana with light account hot/cold lifecycle tracking | [data-streaming](skills/data-streaming/) | -| For stablecoin payment flows and wallet integrations on Solana 200x cheaper token accounts | [payments-and-wallets](skills/payments-and-wallets/) | +| Skill for payment flows using Light Token APIs for sponsored rent-exemption | [payments](skills/payments/) | | For token distribution on Solana 5000x cheaper than SPL (rewards, airdrops, depins, ...) | [token-distribution](skills/token-distribution/) | | For custom ZK Solana programs and privacy-preserving applications to prevent double spending | [zk-nullifier](skills/zk-nullifier/) | | For program development on Solana with infrequently accessed state, such as per-user state, DePIN registrations, ... | [solana-compression](skills/solana-compression/) | @@ -162,23 +162,50 @@ Use rent-free PDAs for: user state, app state, nullifiers for payments, DePIN no * **light-token accounts hold SPL and Token-2022 balances**, not just light-mint balances. * When sponsored rent on a light-token or light-PDA runs out, the account compresses. It decompresses on next interaction. -### Documentation and Examples - -#### TypeScript Client (`@lightprotocol/compressed-token`) +## Payment Flows + +| Name | Description | Docs | Examples | +|------|-------------|------|----------| +| Overview | Learn how the Light Token APIs reduce account creation cost for stablecoin payment infrastructure by 99% with similar developer experience to SPL / Token 2022. | [overview](https://zkcompression.com/light-token/payments/overview) | | +| Basic payment | Send a single token transfer with Light Token APIs for stablecoin payments with comparison to SPL. | [basic-payment](https://zkcompression.com/light-token/payments/basic-payment) | [send-action](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-action.ts) \| [send-instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-instruction.ts) | +| Batch payments | Send payments to multiple recipients in a single transaction or sequentially. | [batch-payments](https://zkcompression.com/light-token/payments/batch-payments) | [batch-send](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/batch-send.ts) | +| Payment with memo | Attach invoice IDs, payment references, or notes to Light Token transfers using Solana's memo program. The memo is recorded in the transaction logs for reconciliation. | [payment-with-memo](https://zkcompression.com/light-token/payments/payment-with-memo) | [payment-with-memo](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/payment-with-memo.ts) | +| Receive payments | Prepare to receive token payments by loading cold accounts and sharing your associated token account address. | [receive-payments](https://zkcompression.com/light-token/payments/receive-payments) | [receive](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/receive/receive.ts) | +| Verify payments | Query token balances and transaction history to verify incoming payments. | [verify-payments](https://zkcompression.com/light-token/payments/verify-payments) | [get-balance](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-balance.ts) \| [get-history](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-history.ts) | +| Verify address | Verify recipient addresses before sending payments. Address validation prevents sending tokens to invalid or unexpected account types. | [verify-recipient-address](https://zkcompression.com/light-token/payments/verify-recipient-address) | [verify-address](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/verify-address.ts) | +| Wrap and unwrap | Move tokens between SPL / Token 2022 and Light Token accounts for interoperability with applications that don't support Light Token yet. | [wrap-unwrap](https://zkcompression.com/light-token/payments/wrap-unwrap) | [wrap](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/wrap.ts) \| [unwrap](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/unwrap.ts) | +| Spend permissions | Delegate token spending to a third party with an amount cap. The delegate can transfer tokens on behalf of the owner up to the approved amount, without the owner signing each transaction. | [spend-permissions](https://zkcompression.com/light-token/payments/spend-permissions) | [delegate-full-flow](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-full-flow.ts) | +| Nullifier PDAs | Create rent-free nullifier PDAs to prevent duplicate actions. | [nullifier-pda](https://zkcompression.com/pda/compressed-pdas/nullifier-pda) | | +| Production readiness | Non-exhaustive checklist for deploying Light Token payment flows to production, including RPC infrastructure, error handling, and security. | [production-readiness](https://zkcompression.com/light-token/payments/production-readiness) | | +| Wallet integration | Guide for Wallet Applications to add Light-token support. | [wallets/overview](https://zkcompression.com/light-token/wallets/overview) | | +| Sign with Privy | Integrate light-token with Privy embedded wallets for rent-free token accounts and transfers. | [privy](https://zkcompression.com/light-token/wallets/privy) | [sign-with-privy](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy) | +| Sign with Wallet Adapter | Integrate light-token with Solana Wallet Adapter for rent-free token accounts and transfers. | [wallet-adapter](https://zkcompression.com/light-token/wallets/wallet-adapter) | [sign-with-wallet-adapter](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-wallet-adapter) | +| Gasless transactions | Abstract SOL fees so users never hold SOL. Sponsor top-ups and transaction fees by setting your application as the fee payer. | [gasless-transactions](https://zkcompression.com/light-token/wallets/gasless-transactions) | [gasless-transactions](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/gasless-transactions) | +| Smart wallets | Send Light Tokens from PDA-based smart wallets. Covers off-curve associated token account creation, instruction-level transfers, and sync and async execution with Squads. | [smart-wallets](https://zkcompression.com/light-token/wallets/smart-wallets) | | + +## Examples + +### TypeScript client (`@lightprotocol/compressed-token`) | Operation | Docs guide | GitHub example | | --------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `createMintInterface` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) | +| `createMintInterface` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-mint.ts) | | `createAtaInterface` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-ata.ts) | | `mintToInterface` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/mint-to.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/mint-to.ts) | | `transferInterface` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/transfer-interface.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/transfer-interface.ts) | | `approve` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-approve.ts) | | `revoke` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-revoke.ts) | +| `delegateTransfer` | [transfer-delegated](https://zkcompression.com/light-token/cookbook/transfer-delegated) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-transfer.ts) | | `wrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/wrap.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/wrap.ts) | | `unwrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/unwrap.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/unwrap.ts) | | `loadAta` | [load-ata](https://zkcompression.com/light-token/cookbook/load-ata) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/load-ata.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/load-ata.ts) | +| `createAtaExplicitRentSponsor` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata-explicit-rent-sponsor.ts) | +| `createSplInterface` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-spl-interface.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-spl-interface.ts) | +| `createSplMint` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-spl-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-spl-mint.ts) | +| `createT22Mint` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-t22-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-t22-mint.ts) | +| `createTokenPool` | — | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-token-pool.ts) | -#### Rust Client (`light_token_client`) +### Rust client (`light-token-client`) | Operation | Docs guide | GitHub example | | -------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -197,38 +224,43 @@ Use rent-free PDAs for: user state, app state, nullifiers for payments, DePIN no | `Close` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/close.rs) | | `Wrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/wrap.rs) | | `Unwrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/unwrap.rs) | -| `SplToLight` | — | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/spl_to_light_transfer.rs) | +| `MintToChecked` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/mint_to_checked.rs) | +| `SplToLightTransfer` | — | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/spl_to_light_transfer.rs) | -#### Program (`light_token`) +### Program examples (`light_token`) -##### Examples -| | Description | -| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | Fork of Raydium AMM that creates markets without paying rent-exemption | -| [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI | -| [pinocchio-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/pinocchio/swap) | Light Token swap reference implementation | +| | Description | +|---------|-------------| +| [escrow](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/escrow) | Peer-to-peer light-token swap with offer/accept flow | +| [fundraiser](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/fundraiser) | Token fundraiser with target, deadline, and refunds | +| [light-token-minter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/light-token-minter) | Create light-mints with metadata, mint tokens | +| [token-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/token-swap) | AMM with liquidity pools and swaps (Anchor) | +| [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | Fork of Raydium AMM that creates markets without paying rent-exemption | +| [pinocchio-swap](https://github.com/Lightprotocol/examples-light-token/tree/main/pinocchio/swap) | AMM with liquidity pools and swaps (Pinocchio) | +| [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI | +| | | -##### Macros +### Program macros (`light_token`) | | Description | | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption | -| [create-ata](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata) | Create associated light-token account | +| [create-ata](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-associated-token-account) | Create associated light-token account | | [create-mint](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) | Create light-token mint | | [create-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) | Create light-token account | -##### CPI Instructions +### CPI instructions (`light_token`) CPI calls can be combined with existing and/or light macros. The API is a superset of SPL-token. | Operation | Docs guide | GitHub example | | ---------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-ata/src/lib.rs) | +| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-associated-token-account/src/lib.rs) | | `CreateTokenAccountCpi` | [create-token-account](https://zkcompression.com/light-token/cookbook/create-token-account) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-token-account/src/lib.rs) | | `CreateMintCpi` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-mint/src/lib.rs) | | `MintToCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/mint-to/src/lib.rs) | -| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) | +| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/mint-to-checked) | | `BurnCpi` | [burn](https://zkcompression.com/light-token/cookbook/burn) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/burn/src/lib.rs) | | `TransferCheckedCpi` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-checked/src/lib.rs) | | `TransferInterfaceCpi` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-interface/src/lib.rs) | @@ -238,16 +270,31 @@ CPI calls can be combined with existing and/or light macros. The API is a supers | `ThawCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/thaw/src/lib.rs) | | `CloseAccountCpi` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/close/src/lib.rs) | -## General References +### Extensions + +| Extension | Docs guide | GitHub example | +|-----------|-----------|----------------| +| Close mint | [close-mint](https://zkcompression.com/light-token/extensions/close-mint) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/close-mint.ts) | +| Confidential transfer | [confidential-transfer](https://zkcompression.com/light-token/extensions/confidential-transfer) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/confidential-transfer.ts) | +| Default account state | [default-account-state](https://zkcompression.com/light-token/extensions/default-account-state) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/default-account-state.ts) | +| Interest-bearing tokens | [interest-bearing-tokens](https://zkcompression.com/light-token/extensions/interest-bearing-tokens) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/interest-bearing-tokens.ts) | +| Metadata and metadata pointer | [metadata-and-metadata-pointer](https://zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/metadata-and-metadata-pointer.ts) | +| Pausable mint | [pausable-mint](https://zkcompression.com/light-token/extensions/pausable-mint) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/pausable-mint.ts) | +| Permanent delegate | [permanent-delegate](https://zkcompression.com/light-token/extensions/permanent-delegate) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/permanent-delegate.ts) | +| Token groups and members | [token-groups-and-members](https://zkcompression.com/light-token/extensions/token-groups-and-members) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/token-groups-and-members.ts) | +| Transfer fees | [transfer-fees](https://zkcompression.com/light-token/extensions/transfer-fees) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-fees.ts) | +| Transfer hook | [transfer-hook](https://zkcompression.com/light-token/extensions/transfer-hook) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-hook.ts) | + +## SDK references -### TypeScript SDK +### TypeScript packages | Package | npm | | --------------------------------- | -------------------------------------------------------------------- | | `@lightprotocol/stateless.js` | [npm](https://www.npmjs.com/package/@lightprotocol/stateless.js) | | `@lightprotocol/compressed-token` | [npm](https://www.npmjs.com/package/@lightprotocol/compressed-token) | -### Rust Crates and SDK +### Rust crates | Crate | docs.rs | | ---------------------------- | -------------------------------------------------------------------------------- | diff --git a/openclaw.plugin.json b/openclaw.plugin.json index ff92d8e..ae514f0 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -29,7 +29,7 @@ "skills/light-sdk", "skills/light-token-client", "skills/solana-compression", - "skills/payments-and-wallets", + "skills/payments", "skills/testing", "skills/zk-nullifier" ], diff --git a/prompts/payments-wallets-distribution/payments.mdx b/prompts/payments-wallets-distribution/payments.mdx index 9b729f0..f26cde0 100644 --- a/prompts/payments-wallets-distribution/payments.mdx +++ b/prompts/payments-wallets-distribution/payments.mdx @@ -9,9 +9,9 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-payments - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js -- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets +- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments SPL → Light Token API mapping: | Operation | SPL | Light Token | @@ -78,9 +78,9 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-payments - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js -- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets +- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments SPL → Light Token API mapping: | Operation | SPL | Light Token | diff --git a/prompts/payments-wallets-distribution/privy.mdx b/prompts/payments-wallets-distribution/privy.mdx index c3c9be8..4f8ecea 100644 --- a/prompts/payments-wallets-distribution/privy.mdx +++ b/prompts/payments-wallets-distribution/privy.mdx @@ -9,7 +9,7 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-privy - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js - Node.js example: https://github.com/Lightprotocol/examples-light-token/tree/main/privy/nodejs - React example: https://github.com/Lightprotocol/examples-light-token/tree/main/privy/react @@ -78,7 +78,7 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-privy - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js - Node.js example: https://github.com/Lightprotocol/examples-light-token/tree/main/privy/nodejs - React example: https://github.com/Lightprotocol/examples-light-token/tree/main/privy/react diff --git a/prompts/payments-wallets-distribution/wallets.mdx b/prompts/payments-wallets-distribution/wallets.mdx index 0a4c0a5..2ad21e2 100644 --- a/prompts/payments-wallets-distribution/wallets.mdx +++ b/prompts/payments-wallets-distribution/wallets.mdx @@ -9,9 +9,9 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-wallets - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js -- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets +- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments SPL → Light Token API mapping: | Operation | SPL | Light Token | @@ -79,9 +79,9 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, T Context: - Guide: https://zkcompression.com/light-token/toolkits/for-wallets - Skills and resources index: https://zkcompression.com/skill.md -- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets +- Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js -- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets +- Full examples: https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments SPL → Light Token API mapping: | Operation | SPL | Light Token | diff --git a/skills/agent-dev-orchestrator/skill.md b/skills/agent-dev-orchestrator/skill.md index 0ebf459..541783a 100644 --- a/skills/agent-dev-orchestrator/skill.md +++ b/skills/agent-dev-orchestrator/skill.md @@ -29,23 +29,25 @@ allowed-tools: ## Capabilities -Light Token allows agents to build scalable Solana applications with rent-free token and mint accounts and PDAs. +ZK Compression is a framework on Solana for stablecoin payment rails, consumer apps and defi. The Light SDK and API's let you create mint, token and PDA accounts >99% cheaper with familiar Solana developer experience. ### Primitives | Primitive | Use case | Constraints | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -| Light Token | Most token use cases (stablecoin rails, agent commerce, DeFi). Rent-free mint and token accounts. \~200x cheaper than SPL and more compute-unit efficient on the hot path. | | +| Light Token | Most token use cases (launchpads, DeFi, payments). Rent-free mint and token accounts. \~200x cheaper than SPL and more compute-unit efficient on the hot path. | | | Light-PDA | DeFi program state such as AMM pools and vaults. \~98% cheaper than PDAs and can be implemented with minimal code changes. | | | Compressed Token | Only for Airdrops and token distribution. Prefer Light Token for other purposes. Used by Light Token under the hood for rent-free storage of inactive Light Tokens. Supported by Phantom and Backpack. | Do not use for general-purpose token features. Use Light Token instead. | | Compressed PDA | User state and app state, nullifiers (payments and ZK applications), DePIN nodes, and stake accounts. Similar to program-derived addresses without a rent-exempt balance. | Not for shared state, pool accounts, or config accounts. Use Light-PDA instead | -### Creation cost +View a complete API comparison to SPL and Solana: https://www.zkcompression.com/api-reference/solana-to-light-comparison. + +### Creation cost and Compute Unit Consumption | Metric | Light | Standard Solana | | ------------------------------------- | -----------------: | --------------: | | **Mint Account** | **~0.00001 SOL** | ~0.0015 SOL | -| **Token Account** | **~0.00001 SOL** | ~0.002 SOL | +| **Token Account** | **~0.00001 SOL** | ~0.0029 SOL | | **PDA (100-byte)** | **~0.0000115 SOL** | ~0.0016 SOL | | **Associated token account creation** | **4,348 CU** | 14,194 CU | | **Transfer** | **312 CU** | 4,645 CU | @@ -57,15 +59,6 @@ Light Token allows agents to build scalable Solana applications with rent-free t npx skills add Lightprotocol/skills ``` -## Security - -This skill does not pull, store, or transmit external secrets. It provides code patterns, documentation references, and development guidance only. - -- **No credentials consumed.** The skill requires no API keys, private keys, or signing secrets. `env: []` is declared explicitly. -- **User-provided configuration.** RPC endpoints, wallet keypairs, and authentication tokens (Privy, wallet adapters) are configured in the user's own application code — the skill only demonstrates how to use them. -- **Install source.** `npx skills add Lightprotocol/skills` installs from the public GitHub repository ([Lightprotocol/skills](https://github.com/Lightprotocol/skills)). Verify the source before running. -- **Audited protocol.** Light Protocol smart contracts are independently audited. Reports are published at [github.com/Lightprotocol/light-protocol/tree/main/audits](https://github.com/Lightprotocol/light-protocol/tree/main/audits). - ## Workflow 1. **Clarify intent** @@ -89,46 +82,15 @@ This skill does not pull, store, or transmit external secrets. It provides code | Use case | Skill | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -| For Solana program development with tokens and PDAs, Light is 200x cheaper than SPL/ Solana and has minimal code differences | [light-sdk](skills/light-sdk/) | -| For client development with tokens on Solana, Light Token is 200x cheaper than SPL and has minimal changes | [light-token-client](skills/light-token-client/) | -| For data pipelines, aggregators, or indexers, real-time account state streaming on Solana with light account hot/cold lifecycle tracking | [data-streaming](skills/data-streaming/) | -| For stablecoin payment flows and wallet integrations on Solana 200x cheaper token accounts | [payments-and-wallets](skills/payments-and-wallets/) | -| For token distribution on Solana 5000x cheaper than SPL (rewards, airdrops, depins, ...) | [token-distribution](skills/token-distribution/) | -| For custom ZK Solana programs and privacy-preserving applications to prevent double spending | [zk-nullifier](skills/zk-nullifier/) | -| For program development on Solana with infrequently accessed state, such as per-user state, DePIN registrations, ... | [solana-compression](skills/solana-compression/) | -| For testing with Light Protocol programs and clients on localnet, devnet, and mainnet validation | [testing](skills/testing/) | -| For questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills | [ask-mcp](skills/ask-mcp/) | - -| Use case | Skill | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| Build rent-free Solana programs with Light SDK (Anchor or Pinocchio). Includes router integration. | [light-sdk](https://github.com/Lightprotocol/skills/tree/main/skills/light-sdk) | -| Use Light Token client SDKs (TypeScript and Rust) for mints, ATAs, transfers | [light-token-client](https://github.com/Lightprotocol/skills/tree/main/skills/light-token-client) | -| Stream account state via Laserstream gRPC | [data-streaming](https://github.com/Lightprotocol/skills/tree/main/skills/data-streaming) | -| Wallets and payment flows with light-token. Includes privy, wallet adapter, mobile wallet adapter signing. Optional nullifier to prevent your onchain instruction from being executed more than once. | [payments-and-wallets](https://github.com/Lightprotocol/skills/tree/main/skills/payments-and-wallets) | -| Airdrops, DePIN, token distribution | [token-distribution](https://github.com/Lightprotocol/skills/tree/main/skills/token-distribution) | -| Anti-double-spend nullifiers for Privacy-preserving ZK programs | [zk-nullifier](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | -| Testing programs and clients on localnet, devnet, mainnet | [testing](https://github.com/Lightprotocol/skills/tree/main/skills/testing) | -| For per-user state, DePIN nodes, and infrequently accessed app state with compressed PDAs | [solana-compression](https://github.com/Lightprotocol/skills/tree/main/skills/solana-compression) | -| Help with Debugging and Questions via DeepWiki MCP | [ask-mcp](https://github.com/Lightprotocol/skills/tree/main/skills/ask-mcp) | - -### Install to Claude Code - -Add the marketplace and install: - -``` -/plugin marketplace add Lightprotocol/skills -/plugin install solana-rent-free-dev -``` - -All skills are included. Use them by name (`/light-sdk`, `/token-distribution`, `/testing`, etc.) or let Claude invoke them based on task context. - -### Install to Cursor - -1. Open Settings (**Cmd+Shift+J** / **Ctrl+Shift+J**) -2. Navigate to **Rules & Commands** → **Project Rules** → **Add Rule** → **Remote Rule (GitHub)** -3. Enter: `https://github.com/Lightprotocol/skills.git` - -Skills are auto-discovered based on context. Ask about light-token, defi, payments, or program migration and the agent uses the relevant skill automatically. +| Skill for payment flows using Light Token APIs for sponsored rent-exemption. | [payments](https://github.com/Lightprotocol/skills/tree/main/skills/payments) | +| For Solana program development with tokens and PDAs, Light is 200x cheaper than SPL/ Solana and has minimal code differences | [light-sdk](https://github.com/Lightprotocol/skills/tree/main/skills/light-sdk) | +| For client development with tokens on Solana, Light Token is 200x cheaper than SPL and has minimal changes | [light-token-client](https://github.com/Lightprotocol/skills/tree/main/skills/light-token-client) | +| For data pipelines, aggregators, or indexers, real-time account state streaming on Solana with light account hot/cold lifecycle tracking | [data-streaming](https://github.com/Lightprotocol/skills/tree/main/skills/data-streaming) | +| For token distribution on Solana 5000x cheaper than SPL (rewards, airdrops, depins, ...) | [token-distribution](https://github.com/Lightprotocol/skills/tree/main/skills/token-distribution) | +| For custom ZK Solana programs and privacy-preserving applications to prevent double spending | [zk-nullifier](https://github.com/Lightprotocol/skills/tree/main/skills/zk-nullifier) | +| For program development on Solana with infrequently accessed state, such as per-user state, DePIN registrations, ... | [solana-compression](https://github.com/Lightprotocol/skills/tree/main/skills/solana-compression) | +| For testing with Light Protocol programs and clients on localnet, devnet, and mainnet validation | [testing](https://github.com/Lightprotocol/skills/tree/main/skills/testing) | +| For questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills | [ask-mcp](https://github.com/Lightprotocol/skills/tree/main/skills/ask-mcp) | ### Install to Any Agent @@ -138,6 +100,8 @@ npx skills add Lightprotocol/skills ## Context +- SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison + ### light-token A token standard functionally equivalent to SPL that stores mint and token accounts more efficiently. @@ -148,7 +112,7 @@ A token standard functionally equivalent to SPL that stores mint and token accou The token program pays rent-exemption cost for you. When an account has no remaining sponsored rent, the account is automatically compressed. Your tokens are cryptographically preserved as a compressed token account (rent-free). The account is loaded into hot account state in-flight when someone interacts with it again. -Use for: Launchpads, DeFi, token transfers, payments, ... . +Use for: Stablecoin Orchestration, Cards, Agent Commerce, Defi, ... . ### light-PDA @@ -186,30 +150,48 @@ Use rent-free PDAs for: user state, app state, nullifiers for payments, DePIN no - **light-token accounts hold SPL and Token-2022 balances**, not just light-mint balances. - When sponsored rent on a light-token or light-PDA runs out, the account compresses. It decompresses on next interaction. -## Examples +## Payment Flows + +| Name | Description | Docs | Examples | +|------|-------------|------|----------| +| Overview | Learn how the Light Token APIs reduce account creation cost for stablecoin payment infrastructure by 99% with similar developer experience to SPL / Token 2022. | [overview](https://zkcompression.com/light-token/payments/overview) | | +| Basic payment | Send a single token transfer with Light Token APIs for stablecoin payments with comparison to SPL. | [basic-payment](https://zkcompression.com/light-token/payments/basic-payment) | [send-action](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-action.ts) \| [send-instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-instruction.ts) | +| Batch payments | Send payments to multiple recipients in a single transaction or sequentially. | [batch-payments](https://zkcompression.com/light-token/payments/batch-payments) | [batch-send](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/batch-send.ts) | +| Payment with memo | Attach invoice IDs, payment references, or notes to Light Token transfers using Solana's memo program. The memo is recorded in the transaction logs for reconciliation. | [payment-with-memo](https://zkcompression.com/light-token/payments/payment-with-memo) | [payment-with-memo](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/payment-with-memo.ts) | +| Receive payments | Prepare to receive token payments by loading cold accounts and sharing your associated token account address. | [receive-payments](https://zkcompression.com/light-token/payments/receive-payments) | [receive](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/receive/receive.ts) | +| Verify payments | Query token balances and transaction history to verify incoming payments. | [verify-payments](https://zkcompression.com/light-token/payments/verify-payments) | [get-balance](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-balance.ts) \| [get-history](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-history.ts) | +| Verify address | Verify recipient addresses before sending payments. Address validation prevents sending tokens to invalid or unexpected account types. | [verify-recipient-address](https://zkcompression.com/light-token/payments/verify-recipient-address) | [verify-address](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/verify-address.ts) | +| Wrap and unwrap | Move tokens between SPL / Token 2022 and Light Token accounts for interoperability with applications that don't support Light Token yet. | [wrap-unwrap](https://zkcompression.com/light-token/payments/wrap-unwrap) | [wrap](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/wrap.ts) \| [unwrap](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/unwrap.ts) | +| Spend permissions | Delegate token spending to a third party with an amount cap. The delegate can transfer tokens on behalf of the owner up to the approved amount, without the owner signing each transaction. | [spend-permissions](https://zkcompression.com/light-token/payments/spend-permissions) | [delegate-full-flow](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-full-flow.ts) | +| Nullifier PDAs | Create rent-free nullifier PDAs to prevent duplicate actions. | [nullifier-pda](https://zkcompression.com/pda/compressed-pdas/nullifier-pda) | | +| Production readiness | Non-exhaustive checklist for deploying Light Token payment flows to production, including RPC infrastructure, error handling, and security. | [production-readiness](https://zkcompression.com/light-token/payments/production-readiness) | | +| Wallet integration | Guide for Wallet Applications to add Light-token support. | [wallets/overview](https://zkcompression.com/light-token/wallets/overview) | | +| Sign with Privy | Integrate light-token with Privy embedded wallets for rent-free token accounts and transfers. | [privy](https://zkcompression.com/light-token/wallets/privy) | [sign-with-privy](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy) | +| Sign with Wallet Adapter | Integrate light-token with Solana Wallet Adapter for rent-free token accounts and transfers. | [wallet-adapter](https://zkcompression.com/light-token/wallets/wallet-adapter) | [sign-with-wallet-adapter](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-wallet-adapter) | +| Gasless transactions | Abstract SOL fees so users never hold SOL. Sponsor top-ups and transaction fees by setting your application as the fee payer. | [gasless-transactions](https://zkcompression.com/light-token/wallets/gasless-transactions) | [gasless-transactions](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/gasless-transactions) | +| Smart wallets | Send Light Tokens from PDA-based smart wallets. Covers off-curve associated token account creation, instruction-level transfers, and sync and async execution with Squads. | [smart-wallets](https://zkcompression.com/light-token/wallets/smart-wallets) | | -### Toolkits - -| | Description | -|---------|-------------| -| [Payments and Wallets](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) | All you need for wallet integrations and payment flows. Minimal API differences to SPL. | -| [Streaming Tokens](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/streaming-tokens/) | Stream mint events using Laserstream | -| [Sign with Privy](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sign-with-privy/) | Light-token operations signed with Privy wallets (Node.js + React) | -| [Sponsor Rent Top-Ups](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/sponsor-rent-top-ups/) | Sponsor rent top-ups for users by setting your application as the fee payer | +## Examples ### TypeScript client (`@lightprotocol/compressed-token`) | Operation | Docs guide | GitHub example | | --------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `createMintInterface` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) | +| `createMintInterface` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-mint.ts) | | `createAtaInterface` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-ata.ts) | | `mintToInterface` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/mint-to.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/mint-to.ts) | | `transferInterface` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/transfer-interface.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/transfer-interface.ts) | | `approve` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-approve.ts) | | `revoke` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-revoke.ts) | +| `delegateTransfer` | [transfer-delegated](https://zkcompression.com/light-token/cookbook/transfer-delegated) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-transfer.ts) | | `wrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/wrap.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/wrap.ts) | | `unwrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/unwrap.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/unwrap.ts) | | `loadAta` | [load-ata](https://zkcompression.com/light-token/cookbook/load-ata) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/load-ata.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/load-ata.ts) | +| `createAtaExplicitRentSponsor` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata-explicit-rent-sponsor.ts) | +| `createSplInterface` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-spl-interface.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-spl-interface.ts) | +| `createSplMint` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-spl-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-spl-mint.ts) | +| `createT22Mint` | — | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-t22-mint.ts) \| [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-t22-mint.ts) | +| `createTokenPool` | — | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-token-pool.ts) | ### Rust client (`light-token-client`) @@ -230,6 +212,8 @@ Use rent-free PDAs for: user state, app state, nullifiers for payments, DePIN no | `Close` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/close.rs) | | `Wrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/wrap.rs) | | `Unwrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/unwrap.rs) | +| `MintToChecked` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/mint_to_checked.rs) | +| `SplToLightTransfer` | — | [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/spl_to_light_transfer.rs) | ### Program examples (`light_token`) @@ -250,7 +234,7 @@ Use rent-free PDAs for: user state, app state, nullifiers for payments, DePIN no | | Description | | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption | -| [create-ata](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata) | Create associated light-token account | +| [create-ata](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-associated-token-account) | Create associated light-token account | | [create-mint](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) | Create light-token mint | | [create-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) | Create light-token account | @@ -260,11 +244,11 @@ CPI calls can be combined with existing and/or light macros. The API is a supers | Operation | Docs guide | GitHub example | | ---------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-ata/src/lib.rs) | +| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-associated-token-account/src/lib.rs) | | `CreateTokenAccountCpi` | [create-token-account](https://zkcompression.com/light-token/cookbook/create-token-account) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-token-account/src/lib.rs) | | `CreateMintCpi` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-mint/src/lib.rs) | | `MintToCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/mint-to/src/lib.rs) | -| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) | +| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [src](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/mint-to-checked) | | `BurnCpi` | [burn](https://zkcompression.com/light-token/cookbook/burn) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/burn/src/lib.rs) | | `TransferCheckedCpi` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-checked/src/lib.rs) | | `TransferInterfaceCpi` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-interface/src/lib.rs) | @@ -274,6 +258,21 @@ CPI calls can be combined with existing and/or light macros. The API is a supers | `ThawCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/thaw/src/lib.rs) | | `CloseAccountCpi` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [src](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/close/src/lib.rs) | +### Extensions + +| Extension | Docs guide | GitHub example | +|-----------|-----------|----------------| +| Close mint | [close-mint](https://zkcompression.com/light-token/extensions/close-mint) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/close-mint.ts) | +| Confidential transfer | [confidential-transfer](https://zkcompression.com/light-token/extensions/confidential-transfer) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/confidential-transfer.ts) | +| Default account state | [default-account-state](https://zkcompression.com/light-token/extensions/default-account-state) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/default-account-state.ts) | +| Interest-bearing tokens | [interest-bearing-tokens](https://zkcompression.com/light-token/extensions/interest-bearing-tokens) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/interest-bearing-tokens.ts) | +| Metadata and metadata pointer | [metadata-and-metadata-pointer](https://zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/metadata-and-metadata-pointer.ts) | +| Pausable mint | [pausable-mint](https://zkcompression.com/light-token/extensions/pausable-mint) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/pausable-mint.ts) | +| Permanent delegate | [permanent-delegate](https://zkcompression.com/light-token/extensions/permanent-delegate) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/permanent-delegate.ts) | +| Token groups and members | [token-groups-and-members](https://zkcompression.com/light-token/extensions/token-groups-and-members) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/token-groups-and-members.ts) | +| Transfer fees | [transfer-fees](https://zkcompression.com/light-token/extensions/transfer-fees) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-fees.ts) | +| Transfer hook | [transfer-hook](https://zkcompression.com/light-token/extensions/transfer-hook) | [example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-hook.ts) | + ## SDK references ### TypeScript packages diff --git a/skills/light-token-client/SKILL.md b/skills/light-token-client/SKILL.md index 8fd502f..88cd008 100644 --- a/skills/light-token-client/SKILL.md +++ b/skills/light-token-client/SKILL.md @@ -58,6 +58,7 @@ Examples show both localnet and devnet configurations. For devnet, set: | Transfer checked | [transfer-checked.md](references/transfer-checked.md) | | Approve delegate | [approve.md](references/approve.md) | | Revoke delegate | [revoke.md](references/revoke.md) | +| Delegate transfer | [delegate-transfer.md](references/delegate-transfer.md) | | Wrap SPL to Light | [wrap.md](references/wrap.md) | | Unwrap Light to SPL | [unwrap.md](references/unwrap.md) | | Load associated token account | [load-associated-token-account.md](references/load-associated-token-account.md) | @@ -68,6 +69,17 @@ Examples show both localnet and devnet configurations. For devnet, set: | Close token account | [close-token-account.md](references/close-token-account.md) | | Mint SPL, wrap, and transfer | [spl-mint-wrap-transfer.md](references/spl-mint-wrap-transfer.md) | | SPL to Light comparison | [spl-to-light.md](references/spl-to-light.md) | +| Token 2022 extensions overview | [extensions/overview.md](references/extensions/overview.md) | +| Metadata + MetadataPointer | [extensions/metadata-and-metadata-pointer.md](references/extensions/metadata-and-metadata-pointer.md) | +| Transfer fees | [extensions/transfer-fees.md](references/extensions/transfer-fees.md) | +| Transfer hook | [extensions/transfer-hook.md](references/extensions/transfer-hook.md) | +| Interest-bearing tokens | [extensions/interest-bearing-tokens.md](references/extensions/interest-bearing-tokens.md) | +| Default account state | [extensions/default-account-state.md](references/extensions/default-account-state.md) | +| Permanent delegate | [extensions/permanent-delegate.md](references/extensions/permanent-delegate.md) | +| Mint close authority | [extensions/close-mint.md](references/extensions/close-mint.md) | +| Token groups and members | [extensions/token-groups-and-members.md](references/extensions/token-groups-and-members.md) | +| Pausable mint | [extensions/pausable-mint.md](references/extensions/pausable-mint.md) | +| Confidential transfer | [extensions/confidential-transfer.md](references/extensions/confidential-transfer.md) | ## Operations overview @@ -82,8 +94,9 @@ Examples show both localnet and devnet configurations. For devnet, set: | Mint to | `mintToInterface` | `MintTo` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | | Transfer | `transferInterface` | `TransferInterface` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | | Transfer checked | — | `TransferChecked` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | -| Approve | `approve` | `Approve` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | -| Revoke | `revoke` | `Revoke` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | +| Approve | `approveInterface` | `Approve` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | +| Revoke | `revokeInterface` | `Revoke` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | +| Delegate transfer | `transferDelegatedInterface` | — | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | | Burn | — | `Burn` | [burn](https://zkcompression.com/light-token/cookbook/burn) | | Burn checked | — | `BurnChecked` | [burn](https://zkcompression.com/light-token/cookbook/burn) | | Wrap SPL to Light | `wrap` | `Wrap` | [wrap-unwrap](https://zkcompression.com/light-token/cookbook/wrap-unwrap) | diff --git a/skills/light-token-client/references/approve.md b/skills/light-token-client/references/approve.md index ec7a977..76d9fe2 100644 --- a/skills/light-token-client/references/approve.md +++ b/skills/light-token-client/references/approve.md @@ -15,8 +15,9 @@ import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, mintToCompressed, - approve, + getAssociatedTokenAddressInterface, } from "@lightprotocol/compressed-token"; +import { approveInterface } from "@lightprotocol/compressed-token/unified"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -28,24 +29,30 @@ const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( - JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), - ), + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) ); (async function () { const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]); + await mintToCompressed(rpc, payer, mint, payer, [ + { recipient: payer.publicKey, amount: 1000n }, + ]); + const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); const delegate = Keypair.generate(); - const tx = await approve( + const tx = await approveInterface( rpc, payer, + senderAta, mint, - 500, - payer, delegate.publicKey, + 500_000, + payer ); + console.log("Approved delegate:", delegate.publicKey.toBase58()); + console.log("Allowance: 500,000 tokens"); console.log("Tx:", tx); })(); ``` @@ -117,6 +124,7 @@ async fn main() -> Result<(), Box> { delegate: delegate.pubkey(), owner: payer.pubkey(), amount: delegate_amount, + fee_payer: payer.pubkey(), } .instruction()?; diff --git a/skills/light-token-client/references/burn-checked.md b/skills/light-token-client/references/burn-checked.md index 96d350e..52b59bb 100644 --- a/skills/light-token-client/references/burn-checked.md +++ b/skills/light-token-client/references/burn-checked.md @@ -22,7 +22,7 @@ async fn main() -> Result<(), Box> { mut rpc, payer, mint, - ata, + associated_token_account, decimals, .. } = setup().await; @@ -31,13 +31,12 @@ async fn main() -> Result<(), Box> { // BurnChecked validates that decimals match the mint let burn_ix = BurnChecked { - source: ata, + source: associated_token_account, mint, amount: burn_amount, decimals, authority: payer.pubkey(), - max_top_up: None, - fee_payer: None, + fee_payer: payer.pubkey(), } .instruction()?; @@ -45,7 +44,7 @@ async fn main() -> Result<(), Box> { .create_and_send_transaction(&[burn_ix], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Balance: {} Tx: {sig}", token.amount); diff --git a/skills/light-token-client/references/burn.md b/skills/light-token-client/references/burn.md index f031120..efd23e6 100644 --- a/skills/light-token-client/references/burn.md +++ b/skills/light-token-client/references/burn.md @@ -33,8 +33,7 @@ async fn main() -> Result<(), Box> { mint, amount: burn_amount, authority: payer.pubkey(), - max_top_up: None, - fee_payer: None, + fee_payer: payer.pubkey(), } .instruction()?; diff --git a/skills/light-token-client/references/create-associated-token-account.md b/skills/light-token-client/references/create-associated-token-account.md index da14dbc..e188e63 100644 --- a/skills/light-token-client/references/create-associated-token-account.md +++ b/skills/light-token-client/references/create-associated-token-account.md @@ -50,7 +50,7 @@ import { Transaction, sendAndConfirmTransaction, } from "@solana/web3.js"; -import { createRpc, LIGHT_TOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; +import { createRpc, CTOKEN_PROGRAM_ID } from "@lightprotocol/stateless.js"; import { createMintInterface, createAssociatedTokenAccountInterfaceInstruction, @@ -85,7 +85,7 @@ const payer = Keypair.fromSecretKey( associatedToken, owner.publicKey, mint, - LIGHT_TOKEN_PROGRAM_ID, + CTOKEN_PROGRAM_ID, ); const tx = new Transaction().add(ix); diff --git a/skills/light-token-client/references/create-mint.md b/skills/light-token-client/references/create-mint.md index 4ad0778..00b01a3 100644 --- a/skills/light-token-client/references/create-mint.md +++ b/skills/light-token-client/references/create-mint.md @@ -194,7 +194,7 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { - let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)).await?; + let mut rpc = LightProgramTest::new(ProgramTestConfig::new(false, None)).await?; let payer = rpc.get_payer().insecure_clone(); let mint_seed = Keypair::new(); diff --git a/skills/light-token-client/references/delegate-transfer.md b/skills/light-token-client/references/delegate-transfer.md new file mode 100644 index 0000000..8504fa4 --- /dev/null +++ b/skills/light-token-client/references/delegate-transfer.md @@ -0,0 +1,78 @@ +# Delegate transfer + +Transfers tokens on behalf of the owner using a delegate's authority. The owner must first approve the delegate via `approveInterface`. + +## TypeScript + +### Action + +```typescript +import "dotenv/config"; +import { Keypair } from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createMintInterface, + mintToCompressed, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; +import { + approveInterface, + transferDelegatedInterface, +} from "@lightprotocol/compressed-token/unified"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await mintToCompressed(rpc, payer, mint, payer, [ + { recipient: payer.publicKey, amount: 1000n }, + ]); + + const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + const delegate = Keypair.generate(); + const recipient = Keypair.generate(); + + // Approve delegate + await approveInterface( + rpc, + payer, + senderAta, + mint, + delegate.publicKey, + 500_000, + payer + ); + console.log("Approved delegate:", delegate.publicKey.toBase58()); + + // Delegate transfers tokens on behalf of the owner + const tx = await transferDelegatedInterface( + rpc, + payer, + senderAta, + mint, + recipient.publicKey, + delegate, + payer.publicKey, + 200_000 + ); + + console.log("Delegated transfer:", tx); +})(); +``` + +## Links + +- [Docs](https://zkcompression.com/light-token/cookbook/approve-revoke) +- [TS example](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-transfer.ts) diff --git a/skills/light-token-client/references/extensions/close-mint.md b/skills/light-token-client/references/extensions/close-mint.md new file mode 100644 index 0000000..1cdbb53 --- /dev/null +++ b/skills/light-token-client/references/extensions/close-mint.md @@ -0,0 +1,104 @@ +# Mint close authority + +Create a Token 2022 mint with the MintCloseAuthority extension and register it with Light Token. + +**Restriction**: Set `compression_only` flag on token accounts. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + ExtensionType, + getMintLen, + createInitializeMint2Instruction, + createInitializeMintCloseAuthorityInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including the MintCloseAuthority extension + const mintLen = getMintLen([ExtensionType.MintCloseAuthority]); + const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption( + mintLen + ); + + // 2. Create the mint account + const createMintAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize the MintCloseAuthority extension + const initMintCloseAuthorityIx = + createInitializeMintCloseAuthorityInstruction( + mintKeypair.publicKey, + payer.publicKey, // close authority + TOKEN_2022_PROGRAM_ID + ); + + // 4. Initialize the mint + const initializeMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // 5. Register the SPL interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createMintAccountIx, + initMintCloseAuthorityIx, + initializeMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/close-mint) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/close-mint.ts) diff --git a/skills/light-token-client/references/extensions/confidential-transfer.md b/skills/light-token-client/references/extensions/confidential-transfer.md new file mode 100644 index 0000000..eb5e287 --- /dev/null +++ b/skills/light-token-client/references/extensions/confidential-transfer.md @@ -0,0 +1,154 @@ +# Confidential transfer + +Create a Token 2022 mint with the ConfidentialTransferMint extension and register it with Light Token. + +**Restriction**: Initialized but not enabled. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + PublicKey, + SystemProgram, + Transaction, + TransactionInstruction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + ExtensionType, + TOKEN_2022_PROGRAM_ID, + createInitializeMint2Instruction, + getMintLen, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +/** + * Build the InitializeMint instruction for ConfidentialTransferMint. + * + * The @solana/spl-token SDK defines ExtensionType.ConfidentialTransferMint + * but does not yet export a helper for this instruction, so we construct + * it manually using the Token-2022 instruction layout. + */ +function createInitializeConfidentialTransferMintIx( + mint: PublicKey, + authority: PublicKey | null, + autoApproveNewAccounts: boolean, + auditorElGamalPubkey: Uint8Array | null, +): TransactionInstruction { + // TokenInstruction::ConfidentialTransferExtension = 27 + // ConfidentialTransferInstruction::InitializeMint = 0 + const data = Buffer.alloc(2 + 1 + 32 + 1 + 1 + 32); + let offset = 0; + data.writeUInt8(27, offset); offset += 1; // TokenInstruction + data.writeUInt8(0, offset); offset += 1; // InitializeMint sub-instruction + + // authority (COption): 1 byte tag + 32 bytes + if (authority) { + data.writeUInt8(1, offset); offset += 1; + authority.toBuffer().copy(data, offset); offset += 32; + } else { + data.writeUInt8(0, offset); offset += 1; + offset += 32; + } + + // auto_approve_new_accounts: bool (1 byte) + data.writeUInt8(autoApproveNewAccounts ? 1 : 0, offset); offset += 1; + + // auditor_elgamal_pubkey (COption): 1 byte tag + 32 bytes + if (auditorElGamalPubkey) { + data.writeUInt8(1, offset); offset += 1; + Buffer.from(auditorElGamalPubkey).copy(data, offset); + } else { + data.writeUInt8(0, offset); offset += 1; + } + + return new TransactionInstruction({ + keys: [{ pubkey: mint, isSigner: false, isWritable: true }], + programId: TOKEN_2022_PROGRAM_ID, + data, + }); +} + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including ConfidentialTransferMint extension + const mintLen = getMintLen([ExtensionType.ConfidentialTransferMint]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // 2. Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize ConfidentialTransferMint extension (must come before mint init) + // auto_approve_new_accounts: false — extension is initialized but not enabled + // auditor_elgamal_pubkey: null — no auditor configured + const initConfidentialTransferIx = + createInitializeConfidentialTransferMintIx( + mintKeypair.publicKey, + payer.publicKey, // authority + false, // auto_approve_new_accounts (not enabled) + null, // auditor_elgamal_pubkey + ); + + // 4. Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID, + ); + + // 5. Register interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initConfidentialTransferIx, + initMintIx, + createSplInterfaceIx, + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/confidential-transfer) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/confidential-transfer.ts) diff --git a/skills/light-token-client/references/extensions/default-account-state.md b/skills/light-token-client/references/extensions/default-account-state.md new file mode 100644 index 0000000..92c41d1 --- /dev/null +++ b/skills/light-token-client/references/extensions/default-account-state.md @@ -0,0 +1,105 @@ +# Default account state + +Create a Token 2022 mint with the DefaultAccountState extension and register it with Light Token. + +**Restriction**: Set `compression_only` flag on token accounts. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + ExtensionType, + getMintLen, + createInitializeMint2Instruction, + createInitializeDefaultAccountStateInstruction, + AccountState, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including the DefaultAccountState extension + const mintLen = getMintLen([ExtensionType.DefaultAccountState]); + const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption( + mintLen + ); + + // 2. Create the mint account + const createMintAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize the DefaultAccountState extension (frozen by default) + const initDefaultAccountStateIx = + createInitializeDefaultAccountStateInstruction( + mintKeypair.publicKey, + AccountState.Frozen, + TOKEN_2022_PROGRAM_ID + ); + + // 4. Initialize the mint + const initializeMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + payer.publicKey, // freeze authority (required for frozen default state) + TOKEN_2022_PROGRAM_ID + ); + + // 5. Register the SPL interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createMintAccountIx, + initDefaultAccountStateIx, + initializeMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/default-account-state) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/default-account-state.ts) diff --git a/skills/light-token-client/references/extensions/interest-bearing-tokens.md b/skills/light-token-client/references/extensions/interest-bearing-tokens.md new file mode 100644 index 0000000..77cdf7e --- /dev/null +++ b/skills/light-token-client/references/extensions/interest-bearing-tokens.md @@ -0,0 +1,104 @@ +# Interest-bearing tokens + +Create a Token 2022 mint with the InterestBearingConfig extension and register it with Light Token. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeInterestBearingMintInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + const rate = 500; // 5% interest rate in basis points + + // Calculate space for mint + InterestBearingConfig extension + const mintLen = getMintLen([ExtensionType.InterestBearingConfig]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize InterestBearingConfig + const initInterestBearingIx = + createInitializeInterestBearingMintInstruction( + mintKeypair.publicKey, + payer.publicKey, // rate authority + rate, + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initInterestBearingIx, + initMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/interest-bearing-tokens) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/interest-bearing-tokens.ts) diff --git a/skills/light-token-client/references/extensions/metadata-and-metadata-pointer.md b/skills/light-token-client/references/extensions/metadata-and-metadata-pointer.md new file mode 100644 index 0000000..dd10c46 --- /dev/null +++ b/skills/light-token-client/references/extensions/metadata-and-metadata-pointer.md @@ -0,0 +1,131 @@ +# Metadata and MetadataPointer + +Create a Token 2022 mint with MetadataPointer and TokenMetadata extensions. The metadata pointer points to the mint itself, storing metadata on-chain without a separate account. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeMetadataPointerInstruction, +} from "@solana/spl-token"; +import { + createInitializeInstruction as createInitializeTokenMetadataInstruction, + pack, + TokenMetadata, +} from "@solana/spl-token-metadata"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + const metadata: TokenMetadata = { + mint: mintKeypair.publicKey, + name: "Example Token", + symbol: "EXT", + uri: "https://example.com/metadata.json", + additionalMetadata: [], + }; + + // Calculate space for mint + MetadataPointer extension + const mintLen = getMintLen([ExtensionType.MetadataPointer]); + const metadataLen = pack(metadata).length; + const totalLen = mintLen + metadataLen; + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(totalLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize MetadataPointer (points to the mint itself) + const initMetadataPointerIx = + createInitializeMetadataPointerInstruction( + mintKeypair.publicKey, + payer.publicKey, + mintKeypair.publicKey, // metadata address = mint itself + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Initialize TokenMetadata on the mint + const initTokenMetadataIx = createInitializeTokenMetadataInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + mint: mintKeypair.publicKey, + metadata: mintKeypair.publicKey, + mintAuthority: payer.publicKey, + name: metadata.name, + symbol: metadata.symbol, + uri: metadata.uri, + updateAuthority: payer.publicKey, + }); + + // Instruction 5: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initMetadataPointerIx, + initMintIx, + initTokenMetadataIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/metadata-and-metadata-pointer.ts) diff --git a/skills/light-token-client/references/extensions/overview.md b/skills/light-token-client/references/extensions/overview.md new file mode 100644 index 0000000..ac75714 --- /dev/null +++ b/skills/light-token-client/references/extensions/overview.md @@ -0,0 +1,36 @@ +# Token 2022 extensions overview + +Create Token 2022 mints with extensions and register them for use with Light Token. Once registered, use the same Light Token APIs (`transferInterface`, `wrap`, `unwrap`) as any other token. + +## Supported extensions + +| Extension | Restriction | Docs | +|-----------|-------------|------| +| MetadataPointer + TokenMetadata | — | [Docs](https://www.zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) | +| TransferFeeConfig | Fees must be zero | [Docs](https://www.zkcompression.com/light-token/extensions/transfer-fees) | +| TransferHook | `program_id` must be nil | [Docs](https://www.zkcompression.com/light-token/extensions/transfer-hook) | +| InterestBearingConfig | — | [Docs](https://www.zkcompression.com/light-token/extensions/interest-bearing-tokens) | +| DefaultAccountState | Set `compression_only` flag on token accounts | [Docs](https://www.zkcompression.com/light-token/extensions/default-account-state) | +| PermanentDelegate | Set `compression_only` flag on token accounts | [Docs](https://www.zkcompression.com/light-token/extensions/permanent-delegate) | +| MintCloseAuthority | Set `compression_only` flag on token accounts | [Docs](https://www.zkcompression.com/light-token/extensions/close-mint) | +| GroupPointer + TokenGroup + GroupMemberPointer + TokenGroupMember | — | [Docs](https://www.zkcompression.com/light-token/extensions/token-groups-and-members) | +| PausableConfig | Set `compression_only` flag on token accounts | [Docs](https://www.zkcompression.com/light-token/extensions/pausable-mint) | +| ConfidentialTransferMint | Initialized but not enabled | [Docs](https://www.zkcompression.com/light-token/extensions/confidential-transfer) | + +## Standard flow + +1. Calculate space for mint account with the extension(s) +2. Get rent-exempt balance for that space +3. Create the mint account (`SystemProgram.createAccount`) +4. Initialize the extension(s) (must come before mint init) +5. Initialize the mint (`createInitializeMint2Instruction`) +6. Register the SPL interface PDA (`LightTokenProgram.createSplInterface`) + +## Not supported + +Scaled UI Amount, Non-Transferable Tokens, Memo Transfer, Immutable Owner, CPI Guard. + +## Links + +- [Docs overview](https://www.zkcompression.com/light-token/extensions/overview) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/extensions) diff --git a/skills/light-token-client/references/extensions/pausable-mint.md b/skills/light-token-client/references/extensions/pausable-mint.md new file mode 100644 index 0000000..3ae9ac8 --- /dev/null +++ b/skills/light-token-client/references/extensions/pausable-mint.md @@ -0,0 +1,102 @@ +# Pausable mint + +Create a Token 2022 mint with the PausableConfig extension and register it with Light Token. + +**Restriction**: Set `compression_only` flag on token accounts. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + ExtensionType, + TOKEN_2022_PROGRAM_ID, + createInitializeMint2Instruction, + createInitializePausableConfigInstruction, + getMintLen, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including the Pausable extension + const mintLen = getMintLen([ExtensionType.PausableConfig]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // 2. Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize Pausable extension (must come before mint init) + const initPausableIx = createInitializePausableConfigInstruction( + mintKeypair.publicKey, + payer.publicKey, // pause authority + TOKEN_2022_PROGRAM_ID, + ); + + // 4. Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID, + ); + + // 5. Register interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initPausableIx, + initMintIx, + createSplInterfaceIx, + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/pausable-mint) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/pausable-mint.ts) diff --git a/skills/light-token-client/references/extensions/permanent-delegate.md b/skills/light-token-client/references/extensions/permanent-delegate.md new file mode 100644 index 0000000..71cae36 --- /dev/null +++ b/skills/light-token-client/references/extensions/permanent-delegate.md @@ -0,0 +1,104 @@ +# Permanent delegate + +Create a Token 2022 mint with the PermanentDelegate extension and register it with Light Token. + +**Restriction**: Set `compression_only` flag on token accounts. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + ExtensionType, + getMintLen, + createInitializeMint2Instruction, + createInitializePermanentDelegateInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including the PermanentDelegate extension + const mintLen = getMintLen([ExtensionType.PermanentDelegate]); + const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption( + mintLen + ); + + // 2. Create the mint account + const createMintAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize the PermanentDelegate extension + const initPermanentDelegateIx = + createInitializePermanentDelegateInstruction( + mintKeypair.publicKey, + payer.publicKey, // permanent delegate authority + TOKEN_2022_PROGRAM_ID + ); + + // 4. Initialize the mint + const initializeMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // 5. Register the SPL interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createMintAccountIx, + initPermanentDelegateIx, + initializeMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/permanent-delegate) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/permanent-delegate.ts) diff --git a/skills/light-token-client/references/extensions/token-groups-and-members.md b/skills/light-token-client/references/extensions/token-groups-and-members.md new file mode 100644 index 0000000..3e7034f --- /dev/null +++ b/skills/light-token-client/references/extensions/token-groups-and-members.md @@ -0,0 +1,190 @@ +# Token groups and members + +Create Token 2022 mints with GroupPointer, TokenGroup, GroupMemberPointer, and TokenGroupMember extensions. Creates a group mint and a member mint, then registers both with Light Token. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + ExtensionType, + getMintLen, + createInitializeMint2Instruction, + createInitializeGroupPointerInstruction, + createInitializeGroupInstruction, + createInitializeGroupMemberPointerInstruction, + createInitializeMemberInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + // ===== Step 1: Create a group mint ===== + const groupMintKeypair = Keypair.generate(); + const decimals = 0; + + // Calculate space including GroupPointer and TokenGroup extensions + const groupMintLen = getMintLen([ + ExtensionType.GroupPointer, + ExtensionType.TokenGroup, + ]); + const groupRentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(groupMintLen); + + // Create the group mint account + const createGroupMintIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: groupRentExemptBalance, + newAccountPubkey: groupMintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: groupMintLen, + }); + + // Initialize GroupPointer (points to the mint itself) + const initGroupPointerIx = createInitializeGroupPointerInstruction( + groupMintKeypair.publicKey, + payer.publicKey, // authority + groupMintKeypair.publicKey, // group address (self-referencing) + TOKEN_2022_PROGRAM_ID + ); + + // Initialize the group mint + const initGroupMintIx = createInitializeMint2Instruction( + groupMintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Initialize the TokenGroup data on the mint + const initGroupIx = createInitializeGroupInstruction({ + group: groupMintKeypair.publicKey, + maxSize: 100, + mint: groupMintKeypair.publicKey, + mintAuthority: payer.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + updateAuthority: payer.publicKey, + }); + + // Register the group mint with Light Token + const registerGroupIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: groupMintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const groupTx = new Transaction().add( + createGroupMintIx, + initGroupPointerIx, + initGroupMintIx, + initGroupIx, + registerGroupIx + ); + + const groupSignature = await sendAndConfirmTransaction(rpc, groupTx, [ + payer, + groupMintKeypair, + ]); + + console.log("Group Mint:", groupMintKeypair.publicKey.toBase58()); + console.log("Group Tx:", groupSignature); + + // ===== Step 2: Create a member mint ===== + const memberMintKeypair = Keypair.generate(); + + // Calculate space including GroupMemberPointer and TokenGroupMember extensions + const memberMintLen = getMintLen([ + ExtensionType.GroupMemberPointer, + ExtensionType.TokenGroupMember, + ]); + const memberRentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(memberMintLen); + + // Create the member mint account + const createMemberMintIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: memberRentExemptBalance, + newAccountPubkey: memberMintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: memberMintLen, + }); + + // Initialize GroupMemberPointer (points to the member mint itself) + const initMemberPointerIx = + createInitializeGroupMemberPointerInstruction( + memberMintKeypair.publicKey, + payer.publicKey, // authority + memberMintKeypair.publicKey, // member address (self-referencing) + TOKEN_2022_PROGRAM_ID + ); + + // Initialize the member mint + const initMemberMintIx = createInitializeMint2Instruction( + memberMintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Initialize the TokenGroupMember data on the member mint + const initMemberIx = createInitializeMemberInstruction({ + group: groupMintKeypair.publicKey, + groupUpdateAuthority: payer.publicKey, + member: memberMintKeypair.publicKey, + memberMint: memberMintKeypair.publicKey, + memberMintAuthority: payer.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + }); + + // Register the member mint with Light Token + const registerMemberIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: memberMintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const memberTx = new Transaction().add( + createMemberMintIx, + initMemberPointerIx, + initMemberMintIx, + initMemberIx, + registerMemberIx + ); + + const memberSignature = await sendAndConfirmTransaction(rpc, memberTx, [ + payer, + memberMintKeypair, + ]); + + console.log("Member Mint:", memberMintKeypair.publicKey.toBase58()); + console.log("Member Tx:", memberSignature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/token-groups-and-members) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/token-groups-and-members.ts) diff --git a/skills/light-token-client/references/extensions/transfer-fees.md b/skills/light-token-client/references/extensions/transfer-fees.md new file mode 100644 index 0000000..f572d28 --- /dev/null +++ b/skills/light-token-client/references/extensions/transfer-fees.md @@ -0,0 +1,107 @@ +# Transfer fees + +Create a Token 2022 mint with the TransferFeeConfig extension and register it with Light Token. + +**Restriction**: Fees must be zero. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeTransferFeeConfigInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // Calculate space for mint + TransferFeeConfig extension + const mintLen = getMintLen([ExtensionType.TransferFeeConfig]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize TransferFeeConfig with zero fees + // Light Token requires fees to be zero + const initTransferFeeIx = createInitializeTransferFeeConfigInstruction( + mintKeypair.publicKey, + payer.publicKey, // transfer fee config authority + payer.publicKey, // withdraw withheld authority + 0, // fee basis points (must be zero) + BigInt(0), // maximum fee (must be zero) + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initTransferFeeIx, + initMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/transfer-fees) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-fees.ts) diff --git a/skills/light-token-client/references/extensions/transfer-hook.md b/skills/light-token-client/references/extensions/transfer-hook.md new file mode 100644 index 0000000..f1ed89f --- /dev/null +++ b/skills/light-token-client/references/extensions/transfer-hook.md @@ -0,0 +1,106 @@ +# Transfer hook + +Create a Token 2022 mint with the TransferHook extension and register it with Light Token. + +**Restriction**: `program_id` must be nil (`PublicKey.default`). + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + PublicKey, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeTransferHookInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // Calculate space for mint + TransferHook extension + const mintLen = getMintLen([ExtensionType.TransferHook]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize TransferHook with nil program_id (hook disabled) + // Light Token requires program_id to be nil (PublicKey.default) + const initTransferHookIx = createInitializeTransferHookInstruction( + mintKeypair.publicKey, + payer.publicKey, // authority + PublicKey.default, // program_id must be nil + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initTransferHookIx, + initMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/transfer-hook) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-hook.ts) diff --git a/skills/light-token-client/references/load-associated-token-account.md b/skills/light-token-client/references/load-associated-token-account.md index ab26306..c7ffaa3 100644 --- a/skills/light-token-client/references/load-associated-token-account.md +++ b/skills/light-token-client/references/load-associated-token-account.md @@ -17,15 +17,15 @@ import { mintToCompressed, loadAta, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( @@ -34,14 +34,19 @@ const payer = Keypair.fromSecretKey( ); (async function () { - // Setup: Get compressed tokens (cold storage) + // Inactive Light Tokens are cryptographically preserved on the Solana ledger + // as compressed tokens (cold storage) + // Setup: Get compressed tokens in light-token associated token account const { mint } = await createMintInterface(rpc, payer, payer, null, 9); await mintToCompressed(rpc, payer, mint, payer, [ { recipient: payer.publicKey, amount: 1000n }, ]); - // Load compressed tokens to hot balance - const lightTokenAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + // Load compressed tokens to light associated token account (hot balance) + const lightTokenAta = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); const tx = await loadAta(rpc, lightTokenAta, payer, mint, payer); console.log("Tx:", tx); @@ -63,7 +68,7 @@ import { mintToCompressed, createLoadAtaInstructions, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -75,8 +80,8 @@ const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( - JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), - ), + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) ); (async function () { @@ -84,15 +89,17 @@ const payer = Keypair.fromSecretKey( // as compressed tokens (cold storage) // Setup: Get compressed tokens in light-token associated token account const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]); + await mintToCompressed(rpc, payer, mint, payer, [ + { recipient: payer.publicKey, amount: 1000n }, + ]); const lightTokenAta = getAssociatedTokenAddressInterface( mint, - payer.publicKey, + payer.publicKey ); - // Load compressed tokens to light associated token account (hot balance). Usually one tx. Empty = noop. - const instructions = await createLoadAtaInstructions( + // Load compressed tokens to light associated token account (hot balance) + const ixs = await createLoadAtaInstructions( rpc, lightTokenAta, payer.publicKey, @@ -100,14 +107,12 @@ const payer = Keypair.fromSecretKey( payer.publicKey, ); - if (instructions.length === 0) return console.log("Nothing to load"); + if (ixs.length === 0) return console.log("Nothing to load"); - for (const ixs of instructions) { - const { blockhash } = await rpc.getLatestBlockhash(); - const tx = buildAndSignTx(ixs, payer, blockhash); - const sig = await sendAndConfirmTx(rpc, tx); - console.log("Tx:", sig); - } + const blockhash = await rpc.getLatestBlockhash(); + const tx = buildAndSignTx(ixs, payer, blockhash.blockhash); + const signature = await sendAndConfirmTx(rpc, tx); + console.log("Tx:", signature); })(); ``` diff --git a/skills/light-token-client/references/mint-to.md b/skills/light-token-client/references/mint-to.md index a4e9c59..ea63215 100644 --- a/skills/light-token-client/references/mint-to.md +++ b/skills/light-token-client/references/mint-to.md @@ -2,7 +2,7 @@ Mints tokens to a Light Token account. -`mintToInterface` auto-detects the token program (SPL, Token 2022, or Light) from the mint address. `fee_payer` and `max_top_up` are optional fields to customize rent top-ups — set to `None` / `undefined` for defaults. +`mintToInterface` auto-detects the token program (SPL, Token 2022, or Light) from the mint address. `fee_payer` specifies who pays rent top-ups. ## TypeScript @@ -204,8 +204,7 @@ async fn main() -> Result<(), Box> { destination: associated_token_account, amount: mint_amount, authority: payer.pubkey(), - max_top_up: None, - fee_payer: None, + fee_payer: payer.pubkey(), } .instruction()?; diff --git a/skills/light-token-client/references/revoke.md b/skills/light-token-client/references/revoke.md index 9759270..af53b2d 100644 --- a/skills/light-token-client/references/revoke.md +++ b/skills/light-token-client/references/revoke.md @@ -15,9 +15,12 @@ import { createRpc } from "@lightprotocol/stateless.js"; import { createMintInterface, mintToCompressed, - approve, - revoke, + getAssociatedTokenAddressInterface, } from "@lightprotocol/compressed-token"; +import { + approveInterface, + revokeInterface, +} from "@lightprotocol/compressed-token/unified"; import { homedir } from "os"; import { readFileSync } from "fs"; @@ -29,23 +32,32 @@ const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( - JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")), - ), + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) ); (async function () { const { mint } = await createMintInterface(rpc, payer, payer, null, 9); - await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]); + await mintToCompressed(rpc, payer, mint, payer, [ + { recipient: payer.publicKey, amount: 1000n }, + ]); + const senderAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); const delegate = Keypair.generate(); - await approve(rpc, payer, mint, 500, payer, delegate.publicKey); - - const delegatedAccounts = await rpc.getCompressedTokenAccountsByDelegate( + await approveInterface( + rpc, + payer, + senderAta, + mint, delegate.publicKey, - { mint }, + 500_000, + payer ); - const tx = await revoke(rpc, payer, delegatedAccounts.items, payer); + console.log("Approved delegate:", delegate.publicKey.toBase58()); + + const tx = await revokeInterface(rpc, payer, senderAta, mint, payer); + console.log("Revoked all delegate permissions"); console.log("Tx:", tx); })(); ``` @@ -108,6 +120,7 @@ async fn main() -> Result<(), Box> { let revoke_instruction = Revoke { token_account: associated_token_account, owner: payer.pubkey(), + fee_payer: payer.pubkey(), } .instruction()?; diff --git a/skills/light-token-client/references/spl-to-light.md b/skills/light-token-client/references/spl-to-light.md index 62c501d..5a9861c 100644 --- a/skills/light-token-client/references/spl-to-light.md +++ b/skills/light-token-client/references/spl-to-light.md @@ -11,8 +11,8 @@ Side-by-side mapping of SPL Token client operations to Light Token equivalents. | Create ATA | `getOrCreateAssociatedTokenAccount` | `createAtaInterface` | `create_associated_token_account` | `CreateAta` | | Mint tokens | `mintTo` | `mintToInterface` | `mint_to` | `MintTo` | | Transfer | `transfer` | `transferInterface` | `transfer` | `TransferInterface` | -| Approve | `approve` | `approve` | `approve` | `Approve` | -| Revoke | `revoke` | `revoke` | `revoke` | `Revoke` | +| Approve | `approve` | `approveInterface` | `approve` | `Approve` | +| Revoke | `revoke` | `revokeInterface` | `revoke` | `Revoke` | | Create token account | — | — | `initialize_account` | `CreateTokenAccount` | | Burn | — | — | `burn` | `Burn` | | Freeze | — | — | `freeze_account` | `Freeze` | @@ -460,8 +460,7 @@ let sig = TransferInterface { destination, amount, decimals, - spl_token_program: None, - restricted: false, + ..Default::default() } .execute(&mut rpc, &payer, &authority) .await?; @@ -508,15 +507,18 @@ const tx = await approve( ```typescript // Light -import { approve } from "@lightprotocol/compressed-token"; +import { approveInterface } from "@lightprotocol/compressed-token/unified"; +import { getAssociatedTokenAddressInterface } from "@lightprotocol/compressed-token"; -const tx = await approve( +const senderAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); +const tx = await approveInterface( rpc, payer, + senderAta, mint, + delegate, amount, - owner, - delegate + owner ); ``` @@ -584,14 +586,11 @@ const tx = await revoke( ```typescript // Light -import { revoke } from "@lightprotocol/compressed-token"; +import { revokeInterface } from "@lightprotocol/compressed-token/unified"; +import { getAssociatedTokenAddressInterface } from "@lightprotocol/compressed-token"; -const tx = await revoke( - rpc, - payer, - delegatedAccounts, - owner -); +const senderAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); +const tx = await revokeInterface(rpc, payer, senderAta, mint, owner); ``` ### Rust action @@ -686,8 +685,7 @@ let ix = Burn { mint, amount, authority: payer.pubkey(), - max_top_up: None, - fee_payer: None, + fee_payer: payer.pubkey(), } .instruction()?; ``` @@ -772,9 +770,9 @@ Move tokens between SPL and Light. No SPL equivalent — this bridges the two sy import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { wrap, - unwrap, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; +import { unwrap } from "@lightprotocol/compressed-token/unified"; const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); @@ -789,33 +787,30 @@ await unwrap(rpc, payer, splAta, owner, mint, amount); ### Rust ```rust -use light_token_client::actions::wrap::Wrap; - -Wrap { - rpc: &mut rpc, - payer: &payer, - spl_ata, - light_ata, - owner: &owner, +use light_token_client::actions::Wrap; + +let sig = Wrap { + source_spl_ata: spl_ata, + destination: light_ata, mint, amount, + decimals, } -.execute() +.execute(&mut rpc, &payer, &payer) .await?; ``` ```rust -use light_token_client::actions::unwrap::Unwrap; +use light_token_client::actions::Unwrap; -Unwrap { - rpc: &mut rpc, - payer: &payer, - spl_ata, - owner: &owner, +let sig = Unwrap { + source: light_ata, + destination_spl_ata: spl_ata, mint, amount, + decimals, } -.execute() +.execute(&mut rpc, &payer, &payer) .await?; ``` @@ -835,7 +830,7 @@ console.log(account.amount); import { getAssociatedTokenAddressInterface, getAtaInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; const ata = getAssociatedTokenAddressInterface(mint, owner); const account = await getAtaInterface(rpc, ata, owner, mint); @@ -867,7 +862,7 @@ Creates the ATA if needed and loads any compressed (cold) state into it. Light T import { loadAta, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; const ata = getAssociatedTokenAddressInterface(mint, recipient); diff --git a/skills/light-token-client/references/transfer-checked.md b/skills/light-token-client/references/transfer-checked.md index 5b045c1..f96977c 100644 --- a/skills/light-token-client/references/transfer-checked.md +++ b/skills/light-token-client/references/transfer-checked.md @@ -103,8 +103,7 @@ async fn main() -> Result<(), Box> { amount: transfer_amount, decimals, authority: payer.pubkey(), - max_top_up: None, - fee_payer: None, + fee_payer: payer.pubkey(), } .instruction()?; diff --git a/skills/light-token-client/references/transfer-interface.md b/skills/light-token-client/references/transfer-interface.md index ffac90b..e1672e4 100644 --- a/skills/light-token-client/references/transfer-interface.md +++ b/skills/light-token-client/references/transfer-interface.md @@ -191,7 +191,7 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(true, None)).await?; + let mut rpc = LightProgramTest::new(ProgramTestConfig::new(true, None)).await?; let payer = rpc.get_payer().insecure_clone(); let decimals = 2u8; @@ -227,8 +227,8 @@ async fn main() -> Result<(), Box> { decimals, authority: payer.pubkey(), payer: payer.pubkey(), + mint, spl_interface: Some(spl_interface), - max_top_up: None, source_owner: spl_token::ID, destination_owner: LIGHT_TOKEN_PROGRAM_ID, } diff --git a/skills/light-token-client/references/unwrap.md b/skills/light-token-client/references/unwrap.md index c9ad921..44aea95 100644 --- a/skills/light-token-client/references/unwrap.md +++ b/skills/light-token-client/references/unwrap.md @@ -9,18 +9,26 @@ Moves tokens from a Light Token associated token account (hot balance) back to a ```typescript import "dotenv/config"; import { Keypair } from "@solana/web3.js"; -import { createRpc, bn } from "@lightprotocol/stateless.js"; -import { createMint, mintTo } from "@lightprotocol/compressed-token"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createMintInterface, + createAtaInterface, + mintToInterface, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; import { unwrap } from "@lightprotocol/compressed-token/unified"; -import { createAssociatedTokenAccount } from "@solana/spl-token"; +import { + createAssociatedTokenAccount, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( @@ -29,18 +37,25 @@ const payer = Keypair.fromSecretKey( ); (async function () { - // Setup: Get compressed tokens (cold storage) - const { mint } = await createMint(rpc, payer, payer.publicKey, 9); - await mintTo(rpc, payer, mint, payer.publicKey, payer, bn(1000)); + // Setup: Create and mint tokens to light-token associated token account + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await createAtaInterface(rpc, payer, mint, payer.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); + await mintToInterface(rpc, payer, mint, destination, payer, 1000); - // Unwrap rent-free tokens to SPL associated token account + // Unwrap light-token to SPL associated token account const splAta = await createAssociatedTokenAccount( rpc, payer, mint, - payer.publicKey + payer.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID ); - const tx = await unwrap(rpc, payer, splAta, payer, mint, bn(500)); + const tx = await unwrap(rpc, payer, splAta, payer, mint, 500); console.log("Tx:", tx); })(); diff --git a/skills/light-token-client/references/wrap.md b/skills/light-token-client/references/wrap.md index fb6c444..48b0d3b 100644 --- a/skills/light-token-client/references/wrap.md +++ b/skills/light-token-client/references/wrap.md @@ -9,24 +9,28 @@ Moves tokens from an SPL or Token-2022 account into a Light Token associated tok ```typescript import "dotenv/config"; import { Keypair } from "@solana/web3.js"; -import { createRpc, bn } from "@lightprotocol/stateless.js"; +import { createRpc } from "@lightprotocol/stateless.js"; import { - createMint, - mintTo, - decompress, + createMintInterface, + createAtaInterface, + mintToInterface, + decompressInterface, wrap, getAssociatedTokenAddressInterface, createAtaInterfaceIdempotent, } from "@lightprotocol/compressed-token"; -import { createAssociatedTokenAccount } from "@solana/spl-token"; +import { + createAssociatedTokenAccount, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( @@ -36,21 +40,31 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Get SPL tokens (needed to wrap) - const { mint } = await createMint(rpc, payer, payer.publicKey, 9); + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await createAtaInterface(rpc, payer, mint, payer.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); + await mintToInterface(rpc, payer, mint, destination, payer, 1000); const splAta = await createAssociatedTokenAccount( rpc, payer, mint, - payer.publicKey + payer.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID ); - await mintTo(rpc, payer, mint, payer.publicKey, payer, bn(1000)); - await decompress(rpc, payer, mint, bn(1000), payer, splAta); + await decompressInterface(rpc, payer, payer, mint, 1000); - // Wrap SPL tokens to rent-free associated token account - const lightTokenAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + // Wrap SPL tokens to light-token associated token account + const lightTokenAta = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); await createAtaInterfaceIdempotent(rpc, payer, mint, payer.publicKey); - const tx = await wrap(rpc, payer, splAta, lightTokenAta, payer, mint, bn(500)); + const tx = await wrap(rpc, payer, splAta, lightTokenAta, payer, mint, 500); console.log("Tx:", tx); })(); @@ -60,26 +74,35 @@ const payer = Keypair.fromSecretKey( ```typescript import "dotenv/config"; -import { Keypair, ComputeBudgetProgram, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; -import { createRpc, bn } from "@lightprotocol/stateless.js"; import { - createMint, - mintTo, - decompress, + Keypair, + ComputeBudgetProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createMintInterface, + createAtaInterface, + mintToInterface, + decompressInterface, createWrapInstruction, getAssociatedTokenAddressInterface, createAtaInterfaceIdempotent, getSplInterfaceInfos, } from "@lightprotocol/compressed-token"; -import { createAssociatedTokenAccount } from "@solana/spl-token"; +import { + createAssociatedTokenAccount, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( @@ -89,18 +112,28 @@ const payer = Keypair.fromSecretKey( (async function () { // Setup: Get SPL tokens (needed to wrap) - const { mint } = await createMint(rpc, payer, payer.publicKey, 9); + const { mint } = await createMintInterface(rpc, payer, payer, null, 9); + await createAtaInterface(rpc, payer, mint, payer.publicKey); + const destination = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); + await mintToInterface(rpc, payer, mint, destination, payer, 1000); const splAta = await createAssociatedTokenAccount( rpc, payer, mint, - payer.publicKey + payer.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID ); - await mintTo(rpc, payer, mint, payer.publicKey, payer, bn(1000)); - await decompress(rpc, payer, mint, bn(1000), payer, splAta); + await decompressInterface(rpc, payer, payer, mint, 1000); // Create wrap instruction - const lightTokenAta = getAssociatedTokenAddressInterface(mint, payer.publicKey); + const lightTokenAta = getAssociatedTokenAddressInterface( + mint, + payer.publicKey + ); await createAtaInterfaceIdempotent(rpc, payer, mint, payer.publicKey); const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); @@ -115,7 +148,7 @@ const payer = Keypair.fromSecretKey( lightTokenAta, payer.publicKey, mint, - bn(500), + 500, splInterfaceInfo, 9, // decimals - must match the mint decimals payer.publicKey diff --git a/skills/payments-and-wallets/references/payments.md b/skills/payments-and-wallets/references/payments.md deleted file mode 100644 index 30e71f1..0000000 --- a/skills/payments-and-wallets/references/payments.md +++ /dev/null @@ -1,346 +0,0 @@ -# Payments - -The light-token API matches SPL-token. Your users receive the same stablecoin, stored more efficiently. - -| Creation cost | SPL | light-token | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**11,000** lamports | - -## API comparison - -| Operation | SPL | light-token (action / instruction) | -|-----------|-----|-------------------------------------| -| Receive | `getOrCreateAssociatedTokenAccount()` | `loadAta()` / `createLoadAtaInstructions()` | -| Transfer | `createTransferInstruction()` | `transferInterface()` / `createTransferInterfaceInstructions()` | -| Get balance | `getAccount()` | `getAtaInterface()` | -| Tx history | `getSignaturesForAddress()` | `rpc.getSignaturesForOwnerInterface()` | -| Wrap from SPL | N/A | `wrap()` / `createWrapInstruction()` | -| Unwrap to SPL | N/A | `unwrap()` / `createUnwrapInstructions()` | -| Register SPL mint | N/A | `createSplInterface()` / `LightTokenProgram.createSplInterface()` | -| Create mint | `createMint()` | `createMintInterface()` | - -Full code examples: [payments-and-wallets](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) - -## Setup - -```bash -npm install @lightprotocol/compressed-token@beta @lightprotocol/stateless.js@beta @solana/web3.js @solana/spl-token -``` - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -import { - createLoadAtaInstructions, - loadAta, - createTransferInterfaceInstructions, - transferInterface, - createUnwrapInstructions, - unwrap, - getAssociatedTokenAddressInterface, - getAtaInterface, - wrap, -} from "@lightprotocol/compressed-token/unified"; - -const rpc = createRpc(RPC_ENDPOINT); -``` - -## Register SPL mint - -Before using light-token interface functions with an existing SPL mint, register it: - -### Check if already registered - -```typescript -import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; - -const infos = await getSplInterfaceInfos(rpc, mint); -const isRegistered = infos.some((i) => i.isInitialized); -``` - -### Register existing SPL mint (instruction) - -```typescript -import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; -import { LightTokenProgram } from "@lightprotocol/compressed-token"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; - -const ix = await LightTokenProgram.createSplInterface({ - feePayer: payer.publicKey, - mint, - tokenProgramId: TOKEN_PROGRAM_ID, -}); - -const tx = new Transaction().add(ix); -await sendAndConfirmTransaction(rpc, tx, [payer]); -``` - -### Register existing SPL mint (action) - -```typescript -import { createSplInterface } from "@lightprotocol/compressed-token"; - -await createSplInterface(rpc, payer, mint); -``` - -### Create new SPL mint with interface - -```typescript -import { createMintInterface } from "@lightprotocol/compressed-token/unified"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; - -const { mint } = await createMintInterface( - rpc, payer, payer, null, 9, undefined, undefined, TOKEN_PROGRAM_ID -); -``` - -## Receive payments - -Load creates the associated token account if needed and loads any compressed state into it. Share the associated token account address with the sender. - -> **About loading**: Light tokens reduce account rent ~200x by auto-compressing inactive accounts. Before any action, the SDK detects compressed state and adds instructions to load it back on-chain. This almost always fits in a single atomic transaction. APIs return `TransactionInstruction[][]` so the same loop handles the rare multi-transaction case automatically. - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createLoadAtaInstructions, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -// Returns TransactionInstruction[][]. -// Each inner array is one transaction. -// Almost always returns just one. -const instructions = await createLoadAtaInstructions( - rpc, - ata, - recipient, - mint, - payer.publicKey -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - - // sign and send ... -} -``` - -### Action - -```typescript -import { - loadAta, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -const sig = await loadAta(rpc, ata, recipient, mint, payer); -if (sig) console.log("Loaded:", sig); -``` - -## Send payments - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified"; - -// Returns TransactionInstruction[][]. -// Each inner array is one transaction. -// Almost always returns just one. -const instructions = await createTransferInterfaceInstructions( - rpc, - payer.publicKey, - mint, - amount, - owner.publicKey, - recipient -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - - // sign and send ... -} -``` - -Sign all transactions in one approval: - -```typescript -const transactions = instructions.map((ixs) => new Transaction().add(...ixs)); - -// One approval for all -const signed = await wallet.signAllTransactions(transactions); - -for (const tx of signed) { - await sendAndConfirmTransaction(rpc, tx); -} -``` - -Optimize sending (parallel conditional loads, then transfer): - -```typescript -import { - createTransferInterfaceInstructions, - sliceLast, -} from "@lightprotocol/compressed-token/unified"; - -const instructions = await createTransferInterfaceInstructions( - rpc, - payer.publicKey, - mint, - amount, - owner.publicKey, - recipient -); -const { rest: loadInstructions, last: transferInstructions } = sliceLast(instructions); -// empty = nothing to load, will no-op. -await Promise.all( - loadInstructions.map((ixs) => { - const tx = new Transaction().add(...ixs); - tx.sign(payer, owner); - return sendAndConfirmTransaction(rpc, tx); - }) -); - -const transferTx = new Transaction().add(...transferInstructions); -transferTx.sign(payer, owner); -await sendAndConfirmTransaction(rpc, transferTx); -``` - -### Action - -```typescript -import { - getAssociatedTokenAddressInterface, - transferInterface, -} from "@lightprotocol/compressed-token/unified"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -// Handles loading, creates recipient associated token account, transfers. -await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount); -``` - -## Show balance - -```typescript -import { - getAssociatedTokenAddressInterface, - getAtaInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, owner); -const account = await getAtaInterface(rpc, ata, owner, mint); - -console.log(account.parsed.amount); -``` - -## Transaction history - -```typescript -const result = await rpc.getSignaturesForOwnerInterface(owner); - -console.log(result.signatures); // Merged + deduplicated -console.log(result.solana); // On-chain txs only -console.log(result.compressed); // Compressed txs only -``` - -Use `getSignaturesForAddressInterface(address)` for address-specific rather than owner-wide history. - -## Wrap from SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createWrapInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - createWrapInstruction( - splAta, - tokenAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals, - payer.publicKey - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - wrap, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount); -``` - -## Unwrap to SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { createUnwrapInstructions } from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -// Each inner array = one transaction. Handles loading + unwrapping together. -const instructions = await createUnwrapInstructions( - rpc, - splAta, - owner.publicKey, - mint, - amount, - payer.publicKey -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - await sendAndConfirmTransaction(rpc, tx, [payer, owner]); -} -``` - -### Action - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { unwrap } from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -await unwrap(rpc, payer, splAta, owner, mint, amount); -``` - -## Source - -- [Payments docs](https://zkcompression.com/light-token/toolkits/for-payments) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) \ No newline at end of file diff --git a/skills/payments-and-wallets/references/wallets.md b/skills/payments-and-wallets/references/wallets.md deleted file mode 100644 index 033b7ec..0000000 --- a/skills/payments-and-wallets/references/wallets.md +++ /dev/null @@ -1,247 +0,0 @@ -# Wallets - -The light-token API matches SPL-token. Your users hold and receive tokens of the same mints, stored more efficiently. - -| Creation cost | SPL | light-token | -| :---------------- | :------------------ | :------------------- | -| **Token Account** | ~2,000,000 lamports | ~**11,000** lamports | - -## API comparison - -| Operation | SPL | light-token (action / instruction) | -|-----------|-----|-------------------------------------| -| Receive | `getOrCreateAssociatedTokenAccount()` | `loadAta()` / `createLoadAtaInstructions()` | -| Transfer | `createTransferInstruction()` | `transferInterface()` / `createTransferInterfaceInstructions()` | -| Get balance | `getAccount()` | `getAtaInterface()` | -| Tx history | `getSignaturesForAddress()` | `rpc.getSignaturesForOwnerInterface()` | -| Wrap from SPL | N/A | `wrap()` / `createWrapInstruction()` | -| Unwrap to SPL | N/A | `unwrap()` / `createUnwrapInstructions()` | - -Full code examples: [payments-and-wallets](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) - -## Setup - -```bash -npm install @lightprotocol/compressed-token@beta @lightprotocol/stateless.js@beta @solana/web3.js @solana/spl-token -``` - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -import { - createLoadAtaInstructions, - loadAta, - createTransferInterfaceInstructions, - transferInterface, - createUnwrapInstructions, - unwrap, - getAssociatedTokenAddressInterface, - getAtaInterface, - wrap, -} from "@lightprotocol/compressed-token/unified"; - -const rpc = createRpc(RPC_ENDPOINT); -``` - -## Receive tokens - -Load creates the associated token account if needed and loads any compressed state into it. Share the associated token account address with the sender. - -> **About loading**: Light tokens reduce account rent ~200x by auto-compressing inactive accounts. Before any action, the SDK detects compressed state and adds instructions to load it back on-chain. This almost always fits in a single atomic transaction. APIs return `TransactionInstruction[][]` so the same loop handles the rare multi-transaction case automatically. - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { - createLoadAtaInstructions, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -// Returns TransactionInstruction[][]. -// Each inner array is one transaction. -// Almost always returns just one. -const instructions = await createLoadAtaInstructions( - rpc, - ata, - recipient, - mint, - payer.publicKey -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - await sendAndConfirmTransaction(rpc, tx, [payer]); -} -``` - -### Action - -```typescript -import { - loadAta, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, recipient); - -const sig = await loadAta(rpc, ata, recipient, mint, payer); -if (sig) console.log("Loaded:", sig); -``` - -## Send tokens - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified"; - -// Each inner array = one transaction. Usually just one. -const instructions = await createTransferInterfaceInstructions( - rpc, - payer.publicKey, - mint, - amount, - owner.publicKey, - recipient -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - await sendAndConfirmTransaction(rpc, tx, [payer, owner]); -} -``` - -### Action - -```typescript -import { - getAssociatedTokenAddressInterface, - transferInterface, -} from "@lightprotocol/compressed-token/unified"; - -const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -// Handles loading, creates recipient associated token account, transfers. -await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount); -``` - -## Show balance - -```typescript -import { - getAssociatedTokenAddressInterface, - getAtaInterface, -} from "@lightprotocol/compressed-token/unified"; - -const ata = getAssociatedTokenAddressInterface(mint, owner); -const account = await getAtaInterface(rpc, ata, owner, mint); - -console.log(account.parsed.amount); -``` - -## Transaction history - -```typescript -const result = await rpc.getSignaturesForOwnerInterface(owner); - -console.log(result.signatures); -console.log(result.solana); -console.log(result.compressed); -``` - -Use `getSignaturesForAddressInterface(address)` for address-specific rather than owner-wide history. - -## Wrap from SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - createWrapInstruction, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; -import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); -const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); - -const tx = new Transaction().add( - createWrapInstruction( - splAta, - tokenAta, - owner.publicKey, - mint, - amount, - splInterfaceInfo, - decimals, - payer.publicKey - ) -); -``` - -### Action helper - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { - wrap, - getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); -const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); - -await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount); -``` - -## Unwrap to SPL - -### Instruction - -```typescript -import { Transaction } from "@solana/web3.js"; -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { createUnwrapInstructions } from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -// Each inner array = one transaction. Handles loading + unwrapping together. -const instructions = await createUnwrapInstructions( - rpc, - splAta, - owner.publicKey, - mint, - amount, - payer.publicKey -); - -for (const ixs of instructions) { - const tx = new Transaction().add(...ixs); - await sendAndConfirmTransaction(rpc, tx, [payer, owner]); -} -``` - -### Action - -```typescript -import { getAssociatedTokenAddressSync } from "@solana/spl-token"; -import { unwrap } from "@lightprotocol/compressed-token/unified"; - -const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); - -await unwrap(rpc, payer, splAta, owner, mint, amount); -``` - -## Source - -- [Wallets docs](https://zkcompression.com/light-token/toolkits/for-wallets) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) \ No newline at end of file diff --git a/skills/payments-and-wallets/SKILL.md b/skills/payments/SKILL.md similarity index 77% rename from skills/payments-and-wallets/SKILL.md rename to skills/payments/SKILL.md index b136776..1fcfab0 100644 --- a/skills/payments-and-wallets/SKILL.md +++ b/skills/payments/SKILL.md @@ -1,6 +1,6 @@ --- -name: payments-and-wallets -description: "For stablecoin payment flows and wallet integrations on Solana 200x cheaper token accounts. Receive, send, balance, history, and client-side signing with Privy and Solana wallet adapters. Optional guide to add nullifiers to prevent payments from being executed more than once." +name: light-payments-skill +description: "Skill for payment flows using Light Token APIs for sponsored rent-exemption." metadata: source: https://github.com/Lightprotocol/skills documentation: https://www.zkcompression.com @@ -11,9 +11,9 @@ metadata: bins: ["node", "cargo"] # node for TS client, cargo for Rust nullifier example --- -# Payments and wallets +# Light Token payments -Build payment flows and wallet integrations using light-token on Solana. The light-token API matches SPL-token and extends it to include the light token program. +Build payment flows using light-token on Solana. The light-token API matches SPL-token and extends it to include the light token program. | Creation cost | SPL | light-token | | :---------------- | :------------------ | :------------------- | @@ -57,12 +57,21 @@ Plural functions (`createTransferInterfaceInstructions`, `createUnwrapInstructio | Task | Reference | |------|-----------| -| Build payment flows (receive, send, balance, history, wrap/unwrap) | [payments.md](references/payments.md) | -| Build wallet UI (display tokens, transfer, wrap/unwrap) | [wallets.md](references/wallets.md) | +| Send payments (basic, batch, memo, sign-all) | [send-payments.md](references/send-payments.md) | +| Receive payments (load ATA, share address) | [receive-payments.md](references/receive-payments.md) | +| Show token balance | [show-balance.md](references/show-balance.md) | +| Verify recipient address | [verify-address.md](references/verify-address.md) | +| Transaction history | [transaction-history.md](references/transaction-history.md) | +| Spend permissions (delegation: approve, revoke, check) | [spend-permissions.md](references/spend-permissions.md) | +| Wrap tokens from SPL to light-token | [wrap-from-spl.md](references/wrap-from-spl.md) | +| Unwrap tokens from light-token to SPL | [unwrap-to-spl.md](references/unwrap-to-spl.md) | +| Register existing SPL mint for light-token | [register-spl-mint.md](references/register-spl-mint.md) | +| Gasless transactions (sponsor fees) | [gasless-transactions.md](references/gasless-transactions.md) | | Sign with Wallet Adapter or Mobile Wallet Adapter | [sign-with-adapter.md](references/sign-with-adapter.md) | | Sign with Privy (embedded wallet provider) | [sign-with-privy.md](references/sign-with-privy.md) | | Prevent duplicate actions (double-spend prevention) | [nullifiers.md](references/nullifiers.md) | | SPL to Light comparison | [spl-to-light.md](references/spl-to-light.md) | +| Token 2022 extensions | [extensions/overview.md](references/extensions/overview.md) | ## Setup @@ -89,9 +98,9 @@ const rpc = createRpc(RPC_ENDPOINT); ## Resources -- [Payments docs](https://zkcompression.com/light-token/toolkits/for-payments) -- [Wallets docs](https://zkcompression.com/light-token/toolkits/for-wallets) -- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) +- [Payments docs](https://zkcompression.com/light-token/payments/integration-guide) +- [Wallets docs](https://zkcompression.com/light-token/wallets/overview) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments) - [Nullifier program](https://github.com/Lightprotocol/nullifier-program/) ## SDK references diff --git a/skills/payments/references/extensions/confidential-transfer.md b/skills/payments/references/extensions/confidential-transfer.md new file mode 100644 index 0000000..ef18eb8 --- /dev/null +++ b/skills/payments/references/extensions/confidential-transfer.md @@ -0,0 +1,154 @@ +# ConfidentialTransferMint + +Create a Token 2022 mint with the ConfidentialTransferMint extension for privacy-preserving payments. + +**Restriction**: Initialized but not enabled. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + PublicKey, + SystemProgram, + Transaction, + TransactionInstruction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + ExtensionType, + TOKEN_2022_PROGRAM_ID, + createInitializeMint2Instruction, + getMintLen, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +/** + * Build the InitializeMint instruction for ConfidentialTransferMint. + * + * The @solana/spl-token SDK defines ExtensionType.ConfidentialTransferMint + * but does not yet export a helper for this instruction, so we construct + * it manually using the Token-2022 instruction layout. + */ +function createInitializeConfidentialTransferMintIx( + mint: PublicKey, + authority: PublicKey | null, + autoApproveNewAccounts: boolean, + auditorElGamalPubkey: Uint8Array | null, +): TransactionInstruction { + // TokenInstruction::ConfidentialTransferExtension = 27 + // ConfidentialTransferInstruction::InitializeMint = 0 + const data = Buffer.alloc(2 + 1 + 32 + 1 + 1 + 32); + let offset = 0; + data.writeUInt8(27, offset); offset += 1; // TokenInstruction + data.writeUInt8(0, offset); offset += 1; // InitializeMint sub-instruction + + // authority (COption): 1 byte tag + 32 bytes + if (authority) { + data.writeUInt8(1, offset); offset += 1; + authority.toBuffer().copy(data, offset); offset += 32; + } else { + data.writeUInt8(0, offset); offset += 1; + offset += 32; + } + + // auto_approve_new_accounts: bool (1 byte) + data.writeUInt8(autoApproveNewAccounts ? 1 : 0, offset); offset += 1; + + // auditor_elgamal_pubkey (COption): 1 byte tag + 32 bytes + if (auditorElGamalPubkey) { + data.writeUInt8(1, offset); offset += 1; + Buffer.from(auditorElGamalPubkey).copy(data, offset); + } else { + data.writeUInt8(0, offset); offset += 1; + } + + return new TransactionInstruction({ + keys: [{ pubkey: mint, isSigner: false, isWritable: true }], + programId: TOKEN_2022_PROGRAM_ID, + data, + }); +} + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including ConfidentialTransferMint extension + const mintLen = getMintLen([ExtensionType.ConfidentialTransferMint]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // 2. Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize ConfidentialTransferMint extension (must come before mint init) + // auto_approve_new_accounts: false — extension is initialized but not enabled + // auditor_elgamal_pubkey: null — no auditor configured + const initConfidentialTransferIx = + createInitializeConfidentialTransferMintIx( + mintKeypair.publicKey, + payer.publicKey, // authority + false, // auto_approve_new_accounts (not enabled) + null, // auditor_elgamal_pubkey + ); + + // 4. Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID, + ); + + // 5. Register interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initConfidentialTransferIx, + initMintIx, + createSplInterfaceIx, + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/confidential-transfer) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/confidential-transfer.ts) diff --git a/skills/payments/references/extensions/metadata-and-metadata-pointer.md b/skills/payments/references/extensions/metadata-and-metadata-pointer.md new file mode 100644 index 0000000..d2d8cf0 --- /dev/null +++ b/skills/payments/references/extensions/metadata-and-metadata-pointer.md @@ -0,0 +1,131 @@ +# MetadataPointer + TokenMetadata + +Add on-chain metadata (name, symbol, URI) to a Token 2022 mint for token branding in payment flows. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeMetadataPointerInstruction, +} from "@solana/spl-token"; +import { + createInitializeInstruction as createInitializeTokenMetadataInstruction, + pack, + TokenMetadata, +} from "@solana/spl-token-metadata"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + const metadata: TokenMetadata = { + mint: mintKeypair.publicKey, + name: "Example Token", + symbol: "EXT", + uri: "https://example.com/metadata.json", + additionalMetadata: [], + }; + + // Calculate space for mint + MetadataPointer extension + const mintLen = getMintLen([ExtensionType.MetadataPointer]); + const metadataLen = pack(metadata).length; + const totalLen = mintLen + metadataLen; + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(totalLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize MetadataPointer (points to the mint itself) + const initMetadataPointerIx = + createInitializeMetadataPointerInstruction( + mintKeypair.publicKey, + payer.publicKey, + mintKeypair.publicKey, // metadata address = mint itself + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Initialize TokenMetadata on the mint + const initTokenMetadataIx = createInitializeTokenMetadataInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + mint: mintKeypair.publicKey, + metadata: mintKeypair.publicKey, + mintAuthority: payer.publicKey, + name: metadata.name, + symbol: metadata.symbol, + uri: metadata.uri, + updateAuthority: payer.publicKey, + }); + + // Instruction 5: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initMetadataPointerIx, + initMintIx, + initTokenMetadataIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/metadata-and-metadata-pointer.ts) diff --git a/skills/payments/references/extensions/overview.md b/skills/payments/references/extensions/overview.md new file mode 100644 index 0000000..37669ea --- /dev/null +++ b/skills/payments/references/extensions/overview.md @@ -0,0 +1,77 @@ +# Token 2022 extensions + +Create Token 2022 mints with extensions and register them for use with Light Token. + +1. Create a Token 2022 mint with one or more extensions +2. Register an interface PDA to hold balances from that mint in Light Token accounts +3. Use the same Light Token APIs (`transferInterface`, `wrap`, `unwrap`) as any other token + +> This skill includes references for payment-relevant extensions. For the complete set, see the `light-token-client` skill. + +## Supported extensions + +| Extension | Restriction | Example | Docs | +|-----------|-------------|---------|------| +| MetadataPointer + TokenMetadata | -- | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/metadata-and-metadata-pointer.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/metadata-and-metadata-pointer) | +| TransferFeeConfig | Fees must be zero | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-fees.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/transfer-fees) | +| TransferHook | `program_id` must be nil | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-hook.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/transfer-hook) | +| InterestBearingConfig | -- | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/interest-bearing-tokens.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/interest-bearing-tokens) | +| DefaultAccountState | Set `compression_only` flag on token accounts | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/default-account-state.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/default-account-state) | +| PermanentDelegate | Set `compression_only` flag on token accounts | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/permanent-delegate.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/permanent-delegate) | +| MintCloseAuthority | Set `compression_only` flag on token accounts | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/close-mint.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/close-mint) | +| GroupPointer + TokenGroup + GroupMemberPointer + TokenGroupMember | -- | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/token-groups-and-members.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/token-groups-and-members) | +| Pausable | Set `compression_only` flag on token accounts | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/pausable-mint.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/pausable-mint) | +| ConfidentialTransferMint | Initialized but not enabled | [Example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/confidential-transfer.ts) | [Docs](https://www.zkcompression.com/light-token/extensions/confidential-transfer) | + +## Creation flow + +```typescript +// 1. Calculate space for mint + extension(s) +const mintLen = getMintLen([ExtensionType.YourExtension]); +const rentExemptBalance = await rpc.getMinimumBalanceForRentExemption(mintLen); + +// 2. Create account +const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, +}); + +// 3. Initialize extension (before mint init) +const initExtensionIx = createInitializeYourExtensionInstruction(/* ... */); + +// 4. Initialize mint +const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, + null, + TOKEN_2022_PROGRAM_ID, +); + +// 5. Register interface PDA with Light Token +const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, +}); + +// 6. Send transaction +const tx = new Transaction().add( + createAccountIx, + initExtensionIx, + initMintIx, + createSplInterfaceIx, +); +``` + +## Not supported + +Scaled UI Amount, Non-Transferable Tokens, Memo Transfer, Immutable Owner, CPI Guard. + +## Links + +- [Extensions overview](https://www.zkcompression.com/light-token/extensions/overview) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/extensions) diff --git a/skills/payments/references/extensions/pausable-mint.md b/skills/payments/references/extensions/pausable-mint.md new file mode 100644 index 0000000..feed43b --- /dev/null +++ b/skills/payments/references/extensions/pausable-mint.md @@ -0,0 +1,102 @@ +# PausableConfig + +Create a Token 2022 mint with the PausableConfig extension for regulatory compliance in payment tokens. + +**Restriction**: Set `compression_only` flag on token accounts. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + ExtensionType, + TOKEN_2022_PROGRAM_ID, + createInitializeMint2Instruction, + createInitializePausableConfigInstruction, + getMintLen, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // 1. Calculate space including the Pausable extension + const mintLen = getMintLen([ExtensionType.PausableConfig]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // 2. Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // 3. Initialize Pausable extension (must come before mint init) + const initPausableIx = createInitializePausableConfigInstruction( + mintKeypair.publicKey, + payer.publicKey, // pause authority + TOKEN_2022_PROGRAM_ID, + ); + + // 4. Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID, + ); + + // 5. Register interface PDA with Light Token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initPausableIx, + initMintIx, + createSplInterfaceIx, + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/pausable-mint) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/pausable-mint.ts) diff --git a/skills/payments/references/extensions/transfer-fees.md b/skills/payments/references/extensions/transfer-fees.md new file mode 100644 index 0000000..e9142c2 --- /dev/null +++ b/skills/payments/references/extensions/transfer-fees.md @@ -0,0 +1,107 @@ +# TransferFeeConfig + +Create a Token 2022 mint with the TransferFeeConfig extension for fee-aware payment tokens. + +**Restriction**: Fees must be zero. + +## TypeScript + +```typescript +import "dotenv/config"; +import { + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { + TOKEN_2022_PROGRAM_ID, + getMintLen, + createInitializeMint2Instruction, + ExtensionType, + createInitializeTransferFeeConfigInstruction, +} from "@solana/spl-token"; +import { homedir } from "os"; +import { readFileSync } from "fs"; + +// devnet: +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); +// localnet: +const rpc = createRpc(); + +const payer = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")) + ) +); + +(async function () { + const mintKeypair = Keypair.generate(); + const decimals = 9; + + // Calculate space for mint + TransferFeeConfig extension + const mintLen = getMintLen([ExtensionType.TransferFeeConfig]); + const rentExemptBalance = + await rpc.getMinimumBalanceForRentExemption(mintLen); + + // Instruction 1: Create account + const createAccountIx = SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + lamports: rentExemptBalance, + newAccountPubkey: mintKeypair.publicKey, + programId: TOKEN_2022_PROGRAM_ID, + space: mintLen, + }); + + // Instruction 2: Initialize TransferFeeConfig with zero fees + // Light Token requires fees to be zero + const initTransferFeeIx = createInitializeTransferFeeConfigInstruction( + mintKeypair.publicKey, + payer.publicKey, // transfer fee config authority + payer.publicKey, // withdraw withheld authority + 0, // fee basis points (must be zero) + BigInt(0), // maximum fee (must be zero) + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 3: Initialize mint + const initMintIx = createInitializeMint2Instruction( + mintKeypair.publicKey, + decimals, + payer.publicKey, // mint authority + null, // freeze authority + TOKEN_2022_PROGRAM_ID + ); + + // Instruction 4: Create SPL interface PDA + // Holds Token-2022 tokens when wrapped to light-token + const createSplInterfaceIx = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint: mintKeypair.publicKey, + tokenProgramId: TOKEN_2022_PROGRAM_ID, + }); + + const tx = new Transaction().add( + createAccountIx, + initTransferFeeIx, + initMintIx, + createSplInterfaceIx + ); + + const signature = await sendAndConfirmTransaction(rpc, tx, [ + payer, + mintKeypair, + ]); + + console.log("Mint:", mintKeypair.publicKey.toBase58()); + console.log("Tx:", signature); +})(); +``` + +## Links + +- [Docs](https://www.zkcompression.com/light-token/extensions/transfer-fees) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/extensions/transfer-fees.ts) diff --git a/skills/payments/references/gasless-transactions.md b/skills/payments/references/gasless-transactions.md new file mode 100644 index 0000000..739a2a9 --- /dev/null +++ b/skills/payments/references/gasless-transactions.md @@ -0,0 +1,132 @@ +# Gasless transactions + +Abstract SOL fees so users never hold SOL. Light Token lets your application cover rent and transaction fees for around 0.001 USD per transaction. + +Your sponsor covers three costs: + +| Cost | Amount | Details | +|:-----|:-------|:--------| +| **Account creation** | ~11,000 lamports (0.001 USD) | Initial bump on virtual rent balance. Rent-exemption is sponsored. | +| **Rent top-ups** | ~766 lamports per write | Fee payer bumps the virtual rent balance on each write to keep accounts active. Set `payer` parameter on any Light Token instruction. | +| **Transaction fees** | ~5,000 lamports per tx | Standard Solana fee payer. Set `feePayer` on the transaction. | + +The `payer` parameter on any Light Token instruction determines who pays rent top-ups in addition to transaction fees. Set your application server as the payer so users never interact with SOL. + +## TypeScript + +### Create a sponsor account + +```typescript +import { Keypair } from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; + +const rpc = createRpc(RPC_ENDPOINT); + +// Sponsor: your application server +const sponsor = Keypair.fromSecretKey(/* your server keypair */); + +// User: only signs to authorize the transfer +const sender = Keypair.fromSecretKey(/* user's keypair */); +``` + +### Create the transfer instruction + +Create the transfer instruction with the sponsor as `payer` and the sender as `authority`. The sender owns the tokens and must sign the transfer. + +```typescript +import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified"; + +const instructions = await createTransferInterfaceInstructions( + rpc, + sponsor.publicKey, // payer: covers rent top-ups and transaction fees + mint, + amount, + sender.publicKey, // authority: user signs to authorize + recipient.publicKey +); +``` + +### Send with both signers + +Both the sponsor and sender must sign the transaction: + +| Role | Parameter | What it does | +|:-----|:----------|:-------------| +| **Payer** (fee payer) | First positional arg | Signs to authorize payment of rent top-ups and transaction fees. Can be your application server. | +| **Authority** (owner) | `owner` / authority arg | Signs to authorize the token transfer. The account holder. | + +```typescript +import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; + +for (const ixs of instructions) { + const tx = new Transaction().add(...ixs); + // Both sponsor and sender must sign + await sendAndConfirmTransaction(rpc, tx, [sponsor, sender]); +} +``` + +## Rust + +### Create a sponsor account + +```rust +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + +// Sponsor: your application server +let sponsor: Keypair = /* your server keypair */; + +// User: only signs to authorize the transfer +let sender: Keypair = /* user's keypair */; +let recipient: Pubkey = /* recipient address */; +let mint: Pubkey = /* e.g. USDC mint */; +``` + +### Create the transfer instruction + +```rust +use light_token::instruction::{ + get_associated_token_address, TransferInterface, LIGHT_TOKEN_PROGRAM_ID, +}; + +let sender_ata = get_associated_token_address(&sender.pubkey(), &mint); +let recipient_ata = get_associated_token_address(&recipient, &mint); + +let transfer_ix = TransferInterface { + source: sender_ata, + destination: recipient_ata, + amount: 500_000, + decimals: 6, + mint, + authority: sender.pubkey(), // user signs to authorize + payer: sponsor.pubkey(), // sponsor covers rent top-ups and transaction fees + spl_interface: None, + source_owner: LIGHT_TOKEN_PROGRAM_ID, + destination_owner: LIGHT_TOKEN_PROGRAM_ID, +} +.instruction()?; +``` + +### Send with both signers + +```rust +let sig = rpc + .create_and_send_transaction( + &[transfer_ix], + &sponsor.pubkey(), // fee payer + &[&sponsor, &sender], // both sign + ) + .await?; + +println!("Tx: {sig}"); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [gasless-transfer.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/gasless-transactions/typescript/gasless-transfer.ts) | Full gasless transfer: sponsor pays all fees, user only signs to authorize. | `createTransferInterfaceInstructions` | + +## Source + +- [Gasless transactions docs](https://zkcompression.com/light-token/wallets/gasless-transactions) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/gasless-transactions/typescript/gasless-transfer.ts) diff --git a/skills/payments-and-wallets/references/nullifiers.md b/skills/payments/references/nullifiers.md similarity index 100% rename from skills/payments-and-wallets/references/nullifiers.md rename to skills/payments/references/nullifiers.md diff --git a/skills/payments/references/receive-payments.md b/skills/payments/references/receive-payments.md new file mode 100644 index 0000000..cbbe2c7 --- /dev/null +++ b/skills/payments/references/receive-payments.md @@ -0,0 +1,61 @@ +# Receive payments + +Load creates the associated token account if needed and loads any compressed state into it. Share the associated token account address with the sender. + +> **About loading**: Light tokens reduce account rent ~200x by auto-compressing inactive accounts. Before any action, the SDK detects compressed state and adds instructions to load it back on-chain. This almost always fits in a single atomic transaction. APIs return `TransactionInstruction[][]` so the same loop handles the rare multi-transaction case automatically. + +## Instruction + +```typescript +import { Transaction } from "@solana/web3.js"; +import { + createLoadAtaInstructions, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; + +const ata = getAssociatedTokenAddressInterface(mint, recipient); + +// Returns TransactionInstruction[][]. +// Each inner array is one transaction. +// Almost always returns just one. +const instructions = await createLoadAtaInstructions( + rpc, + ata, + recipient, + mint, + payer.publicKey +); + +for (const ixs of instructions) { + const tx = new Transaction().add(...ixs); + + // sign and send ... +} +``` + +## Action + +```typescript +import { + loadAta, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; + +const ata = getAssociatedTokenAddressInterface(mint, recipient); + +const sig = await loadAta(rpc, ata, recipient, mint, payer); +if (sig) console.log("Loaded:", sig); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [receive.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/receive/receive.ts) | Prepare to receive: creates the token account if needed, loads compressed state. | `createLoadAtaInstructions` | +| [get-balance.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-balance.ts) | Check token balance for an account. | `getAtaInterface` | +| [get-history.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-history.ts) | List transactions (merged on-chain + compressed). | `getSignaturesForOwnerInterface` | + +## Source + +- [Receive payments docs](https://zkcompression.com/light-token/payments/accept-payments/receive-payments) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments/receive) diff --git a/skills/payments/references/register-spl-mint.md b/skills/payments/references/register-spl-mint.md new file mode 100644 index 0000000..4a7ba09 --- /dev/null +++ b/skills/payments/references/register-spl-mint.md @@ -0,0 +1,59 @@ +# Register SPL mint + +Before using light-token interface functions with an existing SPL mint, register it. + +## Check if already registered + +```typescript +import { getSplInterfaceInfos } from "@lightprotocol/compressed-token"; + +const infos = await getSplInterfaceInfos(rpc, mint); +const isRegistered = infos.some((i) => i.isInitialized); +``` + +## Register existing SPL mint (instruction) + +```typescript +import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; +import { LightTokenProgram } from "@lightprotocol/compressed-token"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; + +const ix = await LightTokenProgram.createSplInterface({ + feePayer: payer.publicKey, + mint, + tokenProgramId: TOKEN_PROGRAM_ID, +}); + +const tx = new Transaction().add(ix); +await sendAndConfirmTransaction(rpc, tx, [payer]); +``` + +## Register existing SPL mint (action) + +```typescript +import { createSplInterface } from "@lightprotocol/compressed-token"; + +await createSplInterface(rpc, payer, mint); +``` + +## Create new SPL mint with interface + +```typescript +import { createMintInterface } from "@lightprotocol/compressed-token"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; + +const { mint } = await createMintInterface( + rpc, payer, payer, null, 9, undefined, undefined, TOKEN_PROGRAM_ID +); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [register-spl-mint.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/register-spl-mint.ts) | One-time: register an interface PDA for an existing SPL mint (e.g. USDC). | `createSplInterface` | + +## Source + +- [Wrap and unwrap docs](https://zkcompression.com/light-token/payments/interop/wrap-unwrap) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/register-spl-mint.ts) diff --git a/skills/payments/references/send-payments.md b/skills/payments/references/send-payments.md new file mode 100644 index 0000000..c387df0 --- /dev/null +++ b/skills/payments/references/send-payments.md @@ -0,0 +1,108 @@ +# Send payments + +The SDK checks cold balances and adds load instructions automatically. The result is `TransactionInstruction[][]` where each inner array is one transaction. Almost always returns just one. + +## Instruction + +```typescript +import { Transaction } from "@solana/web3.js"; +import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified"; + +// Returns TransactionInstruction[][]. +// Each inner array is one transaction. +// Almost always returns just one. +const instructions = await createTransferInterfaceInstructions( + rpc, + payer.publicKey, + mint, + amount, + owner.publicKey, + recipient +); + +for (const ixs of instructions) { + const tx = new Transaction().add(...ixs); + + // sign and send ... +} +``` + +## Action + +```typescript +import { + getAssociatedTokenAddressInterface, + transferInterface, +} from "@lightprotocol/compressed-token/unified"; + +const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); + +// Handles loading, creates recipient associated token account, transfers. +await transferInterface(rpc, payer, sourceAta, mint, recipient, owner, amount); +``` + +## Sign all transactions together + +When a transfer returns multiple transactions (rare), sign them all with one wallet approval: + +```typescript +const transactions = instructions.map((ixs) => new Transaction().add(...ixs)); + +// One approval for all +const signed = await wallet.signAllTransactions(transactions); + +for (const tx of signed) { + await sendAndConfirmTransaction(rpc, tx); +} +``` + +## Optimize sending (parallel loads) + +Use `sliceLast` to separate load transactions from the final transfer, then send loads in parallel: + +```typescript +import { + createTransferInterfaceInstructions, + sliceLast, +} from "@lightprotocol/compressed-token/unified"; + +const instructions = await createTransferInterfaceInstructions( + rpc, + payer.publicKey, + mint, + amount, + owner.publicKey, + recipient +); +const { rest: loadInstructions, last: transferInstructions } = sliceLast(instructions); +// empty = nothing to load, will no-op. +await Promise.all( + loadInstructions.map((ixs) => { + const tx = new Transaction().add(...ixs); + tx.sign(payer, owner); + return sendAndConfirmTransaction(rpc, tx); + }) +); + +const transferTx = new Transaction().add(...transferInstructions); +transferTx.sign(payer, owner); +await sendAndConfirmTransaction(rpc, transferTx); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [send-action.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-action.ts) | Send tokens. One call handles loading and recipient account creation. | `transferInterface` | +| [send-instruction.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/send-instruction.ts) | Same transfer, but returns raw instructions for custom transaction building. | `createTransferInterfaceInstructions` | +| [payment-with-memo.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/payment-with-memo.ts) | Attach an invoice ID or payment reference. Reads it back from transaction logs. | `createTransferInterfaceInstructions`, `sliceLast` | +| [batch-send.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/batch-send.ts) | Pay multiple recipients in one transaction. | `createTransferInterfaceInstructions`, `createAtaInterfaceIdempotent` | +| [sign-all-transactions.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/send/sign-all-transactions.ts) | Sign all transactions with one wallet approval. Shows parallel load optimization. | `signAllTransactions`, `sliceLast` | +| [verify-address.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/verify-address.ts) | Confirm a recipient account exists before sending. | `getAssociatedTokenAddressInterface`, `getAtaInterface` | + +> For gasless transfers (separate fee payer / sponsor), see [gasless-transactions.md](gasless-transactions.md). + +## Source + +- [Send payments docs](https://zkcompression.com/light-token/payments/send-payments/basic-payment) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments/send) diff --git a/skills/payments/references/show-balance.md b/skills/payments/references/show-balance.md new file mode 100644 index 0000000..9a366f0 --- /dev/null +++ b/skills/payments/references/show-balance.md @@ -0,0 +1,24 @@ +# Show balance + +```typescript +import { + getAssociatedTokenAddressInterface, + getAtaInterface, +} from "@lightprotocol/compressed-token"; + +const ata = getAssociatedTokenAddressInterface(mint, owner); +const account = await getAtaInterface(rpc, ata, owner, mint); + +console.log(account.parsed.amount); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [get-balance.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-balance.ts) | Check token balance for an account. | `getAtaInterface` | + +## Source + +- [Verify payments docs](https://zkcompression.com/light-token/payments/accept-payments/verify-payments) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-balance.ts) diff --git a/skills/payments-and-wallets/references/sign-with-adapter.md b/skills/payments/references/sign-with-adapter.md similarity index 100% rename from skills/payments-and-wallets/references/sign-with-adapter.md rename to skills/payments/references/sign-with-adapter.md diff --git a/skills/payments-and-wallets/references/sign-with-privy.md b/skills/payments/references/sign-with-privy.md similarity index 100% rename from skills/payments-and-wallets/references/sign-with-privy.md rename to skills/payments/references/sign-with-privy.md diff --git a/skills/payments/references/spend-permissions.md b/skills/payments/references/spend-permissions.md new file mode 100644 index 0000000..172ef9a --- /dev/null +++ b/skills/payments/references/spend-permissions.md @@ -0,0 +1,74 @@ +# Spend permissions + +Delegation with Light Token works similar to SPL. When you approve a delegate, you authorize a specific account to transfer tokens on your behalf: + +- **Owner retains custody**: You still own the tokens and can transfer or revoke at any time. Delegation is non-custodial. +- **Capped spending**: The delegate can spend tokens up to the limit, but cannot access or drain the account beyond the approved amount. +- **Single delegate per account**: Each token account can only have one active delegate. The owner can revoke at any time. +- **New approval replaces old**: Approving a new delegate automatically revokes the previous one. + +## Use cases + +| Use case | How delegation helps | +|:---------|:--------------------| +| **Subscriptions** | Approve a monthly cap. The service provider transfers the fee each period. | +| **Recurring payments** | Approve a spending limit. The payment processor draws funds as needed. | +| **Managed spending** | A parent or admin approves a cap for a sub-account. | +| **Agent wallets** | An AI agent operates within a delegated spending limit. | + +## Approve a delegate + +Grant a delegate permission to spend up to a capped amount: + +```typescript +import { approveInterface } from "@lightprotocol/compressed-token/unified"; + +const tx = await approveInterface( + rpc, + payer, + senderAta, + mint, + delegate.publicKey, // who gets permission + 500_000, // amount cap + owner // token owner (signs) +); + +console.log("Approved:", tx); +``` + +## Check delegation status + +```typescript +import { getAtaInterface } from "@lightprotocol/compressed-token"; + +const account = await getAtaInterface(rpc, senderAta, owner.publicKey, mint); + +console.log("Delegate:", account.parsed.delegate?.toBase58() ?? "none"); +console.log("Delegated amount:", account.parsed.delegatedAmount.toString()); +``` + +## Revoke a delegate + +Remove spending permission: + +```typescript +import { revokeInterface } from "@lightprotocol/compressed-token/unified"; + +const tx = await revokeInterface(rpc, payer, senderAta, mint, owner); + +console.log("Revoked:", tx); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [delegate-approve.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-approve.ts) | Let a delegate spend tokens on your behalf. | `approveInterface` | +| [delegate-revoke.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-revoke.ts) | Revoke delegate access. | `revokeInterface` | +| [delegate-check.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-check.ts) | Check current delegation status and remaining allowance. | `getAtaInterface` | +| [delegate-full-flow.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/spend-permissions/delegate-full-flow.ts) | Approve, check, and revoke in one script. | `approveInterface`, `revokeInterface`, `getAtaInterface` | + +## Source + +- [Spend permissions docs](https://zkcompression.com/light-token/payments/spend-permissions) +- [GitHub examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments/spend-permissions) diff --git a/skills/payments-and-wallets/references/spl-to-light.md b/skills/payments/references/spl-to-light.md similarity index 95% rename from skills/payments-and-wallets/references/spl-to-light.md rename to skills/payments/references/spl-to-light.md index 6908e3d..22f65de 100644 --- a/skills/payments-and-wallets/references/spl-to-light.md +++ b/skills/payments/references/spl-to-light.md @@ -47,7 +47,7 @@ console.log(account.amount); import { getAssociatedTokenAddressInterface, getAtaInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; const ata = getAssociatedTokenAddressInterface(mint, owner); const account = await getAtaInterface(rpc, ata, owner, mint); @@ -147,9 +147,9 @@ Move tokens between SPL and Light. No SPL equivalent — this bridges the two sy import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { wrap, - unwrap, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; +import { unwrap } from "@lightprotocol/compressed-token/unified"; const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); @@ -169,7 +169,7 @@ Creates the ATA if needed and loads any compressed (cold) state into it. Use thi import { loadAta, getAssociatedTokenAddressInterface, -} from "@lightprotocol/compressed-token/unified"; +} from "@lightprotocol/compressed-token"; const ata = getAssociatedTokenAddressInterface(mint, recipient); @@ -181,4 +181,4 @@ const sig = await loadAta(rpc, ata, recipient, mint, payer); - [Migration reference](https://zkcompression.com/api-reference/solana-to-light-comparison) - [Payments guide](https://zkcompression.com/light-token/toolkits/for-payments) - [Wallets guide](https://zkcompression.com/light-token/toolkits/for-wallets) -- [Payment examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets) +- [Payment examples](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments) diff --git a/skills/payments/references/transaction-history.md b/skills/payments/references/transaction-history.md new file mode 100644 index 0000000..808ae00 --- /dev/null +++ b/skills/payments/references/transaction-history.md @@ -0,0 +1,22 @@ +# Transaction history + +```typescript +const result = await rpc.getSignaturesForOwnerInterface(owner); + +console.log(result.signatures); // Merged + deduplicated +console.log(result.solana); // On-chain txs only +console.log(result.compressed); // Compressed txs only +``` + +Use `getSignaturesForAddressInterface(address)` for address-specific rather than owner-wide history. + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [get-history.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-history.ts) | List transactions (merged on-chain + compressed). | `getSignaturesForOwnerInterface` | + +## Source + +- [Verify payments docs](https://zkcompression.com/light-token/payments/accept-payments/verify-payments) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/get-history.ts) diff --git a/skills/payments/references/unwrap-to-spl.md b/skills/payments/references/unwrap-to-spl.md new file mode 100644 index 0000000..af6dd2f --- /dev/null +++ b/skills/payments/references/unwrap-to-spl.md @@ -0,0 +1,50 @@ +# Unwrap to SPL + +Convert light-token back to SPL. Use this for composing with apps that don't yet support light-token. + +## Instruction + +```typescript +import { Transaction } from "@solana/web3.js"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { createUnwrapInstructions } from "@lightprotocol/compressed-token/unified"; + +const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); + +// Each inner array = one transaction. Handles loading + unwrapping together. +const instructions = await createUnwrapInstructions( + rpc, + splAta, + owner.publicKey, + mint, + amount, + payer.publicKey +); + +for (const ixs of instructions) { + const tx = new Transaction().add(...ixs); + await sendAndConfirmTransaction(rpc, tx, [payer, owner]); +} +``` + +## Action + +```typescript +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { unwrap } from "@lightprotocol/compressed-token/unified"; + +const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); + +await unwrap(rpc, payer, splAta, owner, mint, amount); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [unwrap.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/unwrap.ts) | Convert light-token back to SPL. For composing with apps that don't yet support light-token. | `unwrap` | + +## Source + +- [Wrap and unwrap docs](https://zkcompression.com/light-token/payments/interop/wrap-unwrap) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/unwrap.ts) diff --git a/skills/payments/references/verify-address.md b/skills/payments/references/verify-address.md new file mode 100644 index 0000000..3e789d5 --- /dev/null +++ b/skills/payments/references/verify-address.md @@ -0,0 +1,30 @@ +# Verify address + +Check whether a recipient has an associated token account before sending. + +```typescript +import { + getAssociatedTokenAddressInterface, + getAtaInterface, +} from "@lightprotocol/compressed-token"; + +const ata = getAssociatedTokenAddressInterface(mint, recipient); + +try { + const account = await getAtaInterface(rpc, ata, recipient, mint); + console.log("Account exists, balance:", account.parsed.amount); +} catch { + console.log("Account does not exist yet — will be created on first transfer"); +} +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [verify-address.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/verify-address.ts) | Check if ATA exists for a recipient. | `getAtaInterface` | + +## Source + +- [Verify payments docs](https://zkcompression.com/light-token/payments/accept-payments/verify-payments) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/verify/verify-address.ts) diff --git a/skills/payments/references/wrap-from-spl.md b/skills/payments/references/wrap-from-spl.md new file mode 100644 index 0000000..93c7795 --- /dev/null +++ b/skills/payments/references/wrap-from-spl.md @@ -0,0 +1,60 @@ +# Wrap from SPL + +Convert SPL or Token 2022 tokens to light-token. + +## Instruction + +```typescript +import { Transaction } from "@solana/web3.js"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { + createWrapInstruction, + getAssociatedTokenAddressInterface, + getSplInterfaceInfos, +} from "@lightprotocol/compressed-token"; + +const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); +const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); + +const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint); +const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized); + +const tx = new Transaction().add( + createWrapInstruction( + splAta, + tokenAta, + owner.publicKey, + mint, + amount, + splInterfaceInfo, + decimals, + payer.publicKey + ) +); +``` + +## Action + +```typescript +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { + wrap, + getAssociatedTokenAddressInterface, +} from "@lightprotocol/compressed-token"; + +const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey); +const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey); + +await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount); +``` + +## Examples + +| File | Description | Key function | +|:-----|:------------|:-------------| +| [wrap.ts](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/wrap.ts) | Convert SPL or Token 2022 tokens to light-token. | `wrap` | + +## Source + +- [Wrap and unwrap docs](https://zkcompression.com/light-token/payments/interop/wrap-unwrap) +- [GitHub example](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments/interop/wrap.ts)