-
Notifications
You must be signed in to change notification settings - Fork 10
Rework assetsigner tests #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| # Asset-Signer Wallets | ||
|
|
||
| Every MPL Core asset has a deterministic **signer PDA** that can hold SOL, tokens, and even own other assets. Asset-signer wallets let you use this PDA as your active wallet — all CLI commands automatically operate through the PDA. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| # 1. Create an asset (or use an existing one you own) | ||
| mplx core asset create --name "My Vault" --uri "https://example.com/vault" | ||
|
|
||
| # 2. Register it as a wallet (auto-detects the owner from on-chain data) | ||
| mplx config wallets add vault --asset <assetId> | ||
|
|
||
| # 3. Check the PDA info | ||
| mplx core asset execute info <assetId> | ||
|
|
||
| # 4. Fund the PDA | ||
| mplx toolbox sol transfer 0.1 <signerPdaAddress> | ||
|
|
||
| # 5. Switch to the asset-signer wallet | ||
| mplx config wallets set vault | ||
|
|
||
| # 6. Use any command as the PDA | ||
| mplx toolbox sol balance | ||
| mplx toolbox sol transfer 0.01 <destination> | ||
| mplx core asset create --name "PDA Created NFT" --uri "https://example.com/nft" | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
| When an asset-signer wallet is active: | ||
|
|
||
| 1. **`umi.identity`** is set to a noop signer with the PDA's public key — commands build instructions with the PDA as authority naturally | ||
| 2. **`umi.payer`** is also set to the PDA noop signer — so derived addresses (ATAs, token accounts) resolve correctly | ||
| 3. **At send time**, the transaction is wrapped in MPL Core's `execute` instruction, which signs on behalf of the PDA on-chain | ||
| 4. **The real wallet** (asset owner) signs the outer transaction and pays fees via `setFeePayer` | ||
|
|
||
| ## Wallet Management | ||
|
|
||
| ### Adding an Asset-Signer Wallet | ||
|
|
||
| ```bash | ||
| mplx config wallets add <name> --asset <assetId> | ||
| ``` | ||
|
|
||
| The CLI fetches the asset on-chain, determines the owner, and matches it against your saved wallets. If the owner isn't in your wallet list, you'll be prompted to add it first. | ||
|
|
||
| ### Listing Wallets | ||
|
|
||
| ```bash | ||
| mplx config wallets list | ||
| ``` | ||
|
|
||
| Asset-signer wallets show as `asset-signer` type with the PDA address and linked asset. | ||
|
|
||
| ### Switching Wallets | ||
|
|
||
| ```bash | ||
| # Switch to asset-signer | ||
| mplx config wallets set vault | ||
|
|
||
| # Switch back to normal | ||
| mplx config wallets set my-wallet | ||
| ``` | ||
|
|
||
| ### Overriding with -k | ||
|
|
||
| Pass `-k` to bypass the asset-signer wallet for a single command: | ||
|
|
||
| ```bash | ||
| # Uses the specified keypair directly, ignores asset-signer | ||
| mplx toolbox sol balance -k /path/to/wallet.json | ||
| ``` | ||
|
|
||
| ## Separate Fee Payer | ||
|
|
||
| The on-chain `execute` instruction supports separate authority and fee payer accounts. Use `-p` to have a different wallet pay transaction fees while the asset owner signs the execute: | ||
|
|
||
| ```bash | ||
| mplx toolbox sol transfer 0.01 <destination> -p /path/to/fee-payer.json | ||
| ``` | ||
|
|
||
| The asset owner still signs the `execute` instruction. The `-p` wallet only pays the transaction fee. | ||
|
|
||
| ## Supported Commands | ||
|
|
||
| All CLI commands work with asset-signer wallets. The transaction wrapping happens transparently in the send layer. | ||
|
|
||
| ### Fully Transparent (no special handling needed) | ||
|
|
||
| - **Core**: `asset create`, `asset transfer`, `asset burn`, `asset update`, `collection create` | ||
| - **Toolbox SOL**: `balance`, `transfer`, `wrap`, `unwrap` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 92 includes Suggested doc fix-- **Toolbox SOL**: `balance`, `transfer`, `wrap`, `unwrap`
+- **Toolbox SOL**: `balance`, `transfer`- **SOL wrapping** — creating wrapped SOL (wSOL) token accounts fails in CPI context
+ **SOL wrapping** — `wrap`/`unwrap` flows that create or fund wSOL token accounts fail in CPI contextAlso applies to: 123-123 🤖 Prompt for AI Agents |
||
| - **Toolbox Token**: `transfer`, `create`, `mint` | ||
| - **Toolbox Raw**: `raw --instruction <base64>` | ||
| - **Token Metadata**: `transfer`, `create`, `update` | ||
| - **Bubblegum**: `nft create` (into existing trees — tree creation itself is a [CPI limitation](#cpi-limitations)), `nft transfer`, `nft burn`, `collection create` | ||
| - **Genesis**: `create`, `bucket add-*`, `deposit`, `withdraw`, `claim`, `finalize`, `revoke` | ||
| - **Distribution**: `create`, `deposit`, `withdraw` | ||
| - **Candy Machine**: `insert`, `withdraw` | ||
|
|
||
| ### PDA Inspection | ||
|
|
||
| ```bash | ||
| # Show the PDA address and SOL balance for any asset | ||
| mplx core asset execute info <assetId> | ||
| ``` | ||
|
|
||
| ### Raw Instructions | ||
|
|
||
| ```bash | ||
| # Execute arbitrary base64-encoded instructions as the current wallet | ||
| # When asset-signer is active, automatically wrapped in execute() | ||
| mplx toolbox raw --instruction <base64> | ||
| mplx toolbox raw --instruction <ix1> --instruction <ix2> | ||
| echo "<base64>" | mplx toolbox raw --stdin | ||
| ``` | ||
|
|
||
| ## CPI Limitations | ||
|
|
||
| Some operations cannot be wrapped in `execute()` due to Solana CPI constraints: | ||
|
|
||
| - **Large account creation** — Merkle trees, candy machines (exceed CPI account allocation limits) | ||
| - **SOL wrapping** — creating wrapped SOL (wSOL) token accounts fails in CPI context | ||
|
|
||
| For these operations, use a normal wallet or create the infrastructure first, then switch to the asset-signer wallet for subsequent operations. | ||
|
|
||
| ## Building Raw Instructions | ||
|
|
||
| The CLI includes serialization helpers for building base64-encoded instructions: | ||
|
|
||
| ```typescript | ||
| import { publicKey } from '@metaplex-foundation/umi' | ||
| import { serializeInstruction } from '@metaplex-foundation/cli/lib/execute/deserializeInstruction' | ||
|
|
||
| const signerPda = '<PDA address from execute info>' | ||
| const destination = '<destination address>' | ||
|
|
||
| // System Program SOL transfer | ||
| const data = new Uint8Array(12) | ||
| const view = new DataView(data.buffer) | ||
| view.setUint32(0, 2, true) // Transfer discriminator | ||
| view.setBigUint64(4, 1_000_000n, true) // 0.001 SOL | ||
|
|
||
| const ix = { | ||
| programId: publicKey('11111111111111111111111111111111'), | ||
| keys: [ | ||
| { pubkey: publicKey(signerPda), isSigner: true, isWritable: true }, | ||
| { pubkey: publicKey(destination), isSigner: false, isWritable: true }, | ||
| ], | ||
| data, | ||
| } | ||
|
|
||
| console.log(serializeInstruction(ix)) | ||
| // Pass the output to: mplx toolbox raw --instruction <base64> | ||
| ``` | ||
|
|
||
| ### Instruction Binary Format | ||
|
|
||
| | Bytes | Field | | ||
| |-------|-------| | ||
| | 32 | Program ID | | ||
| | 2 | Number of accounts (u16 little-endian) | | ||
| | 33 per account | 32 bytes pubkey + 1 byte flags (bit 0 = isSigner, bit 1 = isWritable) | | ||
| | remaining | Instruction data | | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| # Test Wallet Modes | ||
|
|
||
| The test suite runs in two wallet modes to verify all commands work with both normal keypairs and asset-signer wallets. | ||
|
|
||
| ```bash | ||
| npm test # Both modes sequentially | ||
| npm run test:normal # Normal wallet mode only | ||
| npm run test:asset-signer # Asset-signer wallet mode only | ||
| ``` | ||
|
|
||
| In asset-signer mode, a root hook (`test/setup.asset-signer.ts`) creates a signing asset, funds its PDA, and writes a temporary config. `runCli` then uses `-c <config>` instead of `-k <keypair>`, so all transactions are wrapped in MPL Core `execute()`. | ||
|
|
||
| Infrastructure that can't be created via execute CPI (large account allocations) uses `runCliDirect`, which always uses the normal keypair. | ||
|
|
||
| ## Test Coverage by Wallet Mode | ||
|
|
||
| **Legend:** | ||
| - **Y** = runs and passes | ||
| - **Skip** = skipped (CPI limitation or authority mismatch) | ||
| - **Pending** = pre-existing `describe.skip` (not related to asset-signer) | ||
|
|
||
| ### Core Commands | ||
|
|
||
| | Test | Normal | Asset-Signer | | ||
| |---|---|---| | ||
| | core asset create (name/uri, collection, custom owner) | Y | Y | | ||
| | core asset transfer (standalone, collection, not-owner error) | Y | Y | | ||
| | core asset burn (standalone, collection) | Y | Y | | ||
| | core asset update | Y | Y | | ||
| | core collection create | Y | Y | | ||
| | core plugins (add/update on collection and asset) | Y | Y | | ||
| | core execute info (PDA address + balance) | Y | Y | | ||
|
|
||
| ### Asset-Signer Specific | ||
|
|
||
| | Test | Normal | Asset-Signer | | ||
| |---|---|---| | ||
| | Separate fee payer via `-p` | Y | Y | | ||
| | Mint cNFT into public tree as PDA | Y | Y | | ||
|
|
||
| ### Toolbox | ||
|
|
||
| | Test | Normal | Asset-Signer | | ||
| |---|---|---| | ||
| | sol balance (identity + specific address) | Y | Y | | ||
| | sol transfer | Y | Y | | ||
| | sol wrap | Y | Y | | ||
| | sol unwrap | Y | Y | | ||
| | token create | Y | Y | | ||
| | token mint | Y | Y | | ||
| | toolbox raw (execute + error) | Y | Y | | ||
|
|
||
| ### Token Metadata | ||
|
|
||
| | Test | Normal | Asset-Signer | | ||
| |---|---|---| | ||
| | tm transfer (NFT + pNFT) | Y | Y | | ||
| | tm transfer validation errors | Y | Y | | ||
| | tm update validation errors | Y | Y | | ||
|
|
||
| ### Bubblegum | ||
|
|
||
| | Test | Normal | Asset-Signer | Notes | | ||
| |---|---|---|---| | ||
| | bg tree create (8 tests) | Y | Y | Uses `runCliDirect` internally (CPI limitation) | | ||
| | bg collection create (9 tests) | Y | Y | No trees involved | | ||
| | bg nft create (9 tests) | Y | Skip | Tree authority mismatch — tree owned by wallet, PDA can't mint | | ||
| | bg integration (8 tests) | Y | Skip | Tree authority mismatch | | ||
| | bg nft burn | Pending | Pending | Pre-existing `describe.skip` | | ||
| | bg nft transfer | Pending | Pending | Pre-existing `describe.skip` | | ||
| | bg nft update | Pending | Pending | Pre-existing `describe.skip` | | ||
|
|
||
| ### Candy Machine | ||
|
|
||
| | Test | Normal | Asset-Signer | Notes | | ||
| |---|---|---|---| | ||
| | cm create (3 on-chain tests) | Y | Skip | CM creation is CPI-incompatible (large account) | | ||
| | cm create hasGuards (5 unit tests) | Y | Y | Pure unit tests | | ||
| | cm full lifecycle (create → insert → withdraw) | Y | Skip | CM authority mismatch | | ||
| | cm insert | Y | Skip | CM authority mismatch | | ||
| | cm withdraw | Y | Skip | CM authority mismatch | | ||
| | cm guard parsing (5 unit tests) | Y | Y | Pure unit tests | | ||
|
|
||
| ### Genesis | ||
|
|
||
| | Test | Normal | Asset-Signer | Notes | | ||
| |---|---|---|---| | ||
| | genesis create/fetch (7 tests) | Y | Y | Setup uses `runCliDirect` for SOL wrap | | ||
| | genesis integration (19 tests) | Y | Skip | Authority mismatch on deposits/finalize | | ||
| | genesis launch (12 tests) | Y | Y | Setup uses `runCliDirect` for SOL wrap | | ||
| | genesis presale (6 tests) | Y | Skip | Authority mismatch on deposits/claims | | ||
| | genesis withdraw (8 tests) | Y | Skip | Authority mismatch on deposits/withdrawals | | ||
|
|
||
| ### Distribution | ||
|
|
||
| | Test | Normal | Asset-Signer | Notes | | ||
| |---|---|---|---| | ||
| | distro deposit (6 tests) | Y | Skip | Authority mismatch + token account mismatch | | ||
| | distro withdraw (8 tests) | Y | Skip | Authority mismatch + token account mismatch | | ||
|
|
||
| ### Lib (Unit Tests) | ||
|
|
||
| | Test | Normal | Asset-Signer | | ||
| |---|---|---| | ||
| | deserializeInstruction roundtrip (6 tests) | Y | Y | | ||
|
|
||
| ## Why Some Tests Skip in Asset-Signer Mode | ||
|
|
||
| ### CPI Limitations | ||
|
|
||
| These operations allocate large accounts, which fails when wrapped in `execute()`: | ||
|
|
||
| - Merkle tree creation | ||
| - Candy machine creation | ||
|
|
||
| The `createBubblegumTree` helper and CM creation in test setup use `runCliDirect` to bypass this. | ||
|
|
||
| ### Authority Mismatch | ||
|
|
||
| Resources created with `runCliDirect` (normal wallet) have the wallet as authority. In asset-signer mode, commands run as the PDA, which isn't the authority. This affects: | ||
|
|
||
| - bg nft minting (tree authority is wallet, not PDA) | ||
| - CM insert/withdraw (CM authority is wallet) | ||
| - Genesis deposits/finalize (genesis authority is wallet) | ||
| - Distro deposits/withdrawals (distro authority is wallet) | ||
|
|
||
| ### What IS Tested in Asset-Signer Mode | ||
|
|
||
| The asset-signer-specific test creates a **public tree** (anyone can mint) and verifies the PDA can mint a cNFT into it. This confirms bubblegum minting works through execute CPI when authority isn't an issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overbroad support statement conflicts with documented limitations.
Line 87 says all commands work, but Lines 118–125 explicitly document unsupported CPI cases. Reword this to avoid promising universal compatibility.
Suggested doc fix
📝 Committable suggestion
🤖 Prompt for AI Agents