-
Notifications
You must be signed in to change notification settings - Fork 10
Add core execute commands #85
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
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,17 @@ | ||
| import { Command } from '@oclif/core' | ||
|
|
||
| export default class CoreAssetExecute extends Command { | ||
| static override description = 'Execute instructions signed by an MPL Core Asset\'s signer PDA' | ||
|
|
||
| static override examples = [ | ||
| '<%= config.bin %> core asset execute signer <assetId>', | ||
| '<%= config.bin %> core asset execute transfer-sol <assetId> --amount 0.5 --destination <address>', | ||
| '<%= config.bin %> core asset execute transfer-token <assetId> --mint <mint> --amount 1000 --destination <address>', | ||
| '<%= config.bin %> core asset execute transfer-asset <assetId> --asset <targetAssetId> --new-owner <address>', | ||
| '<%= config.bin %> core asset execute raw <assetId> --instruction <base64>', | ||
| ] | ||
|
|
||
| public async run(): Promise<void> { | ||
| const {args, flags} = await this.parse(CoreAssetExecute) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,132 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { execute, fetchAsset, fetchCollection, findAssetSignerPda } from '@metaplex-foundation/mpl-core' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { publicKey } from '@metaplex-foundation/umi' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Args, Flags } from '@oclif/core' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ora from 'ora' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { generateExplorerUrl } from '../../../../explorers.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TransactionCommand } from '../../../../TransactionCommand.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { txSignatureToString } from '../../../../lib/util.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { deserializeInstruction } from '../../../../lib/execute/deserializeInstruction.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default class ExecuteRaw extends TransactionCommand<typeof ExecuteRaw> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static override description = `Execute arbitrary instructions signed by an asset's signer PDA. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Instructions must be base64-encoded serialized Solana instructions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Each instruction should be constructed with the asset's signer PDA as the signer. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Use --instruction for each instruction to include (can be repeated). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Alternatively, pipe instructions via stdin with --stdin.` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static override examples = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '<%= config.bin %> <%= command.id %> <assetId> --instruction <base64EncodedInstruction>', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '<%= config.bin %> <%= command.id %> <assetId> --instruction <ix1> --instruction <ix2>', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'echo "<base64>" | <%= config.bin %> <%= command.id %> <assetId> --stdin', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static override args = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assetId: Args.string({ description: 'Asset whose signer PDA will sign the instructions', required: true }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static override flags = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instruction: Flags.string({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| char: 'i', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: 'Base64-encoded instruction (can be repeated)', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| multiple: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stdin: Flags.boolean({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: 'Read base64-encoded instructions from stdin (one per line)', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exclusive: ['instruction'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async readStdin(): Promise<string[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let data = '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.stdin.setEncoding('utf8') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('data', (chunk) => { data += chunk }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('end', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lines = data.split('\n').map(l => l.trim()).filter(l => l.length > 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolve(lines) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('error', reject) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+53
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. 🧹 Nitpick | 🔵 Trivial Consider adding a TTY check or timeout for stdin reading. If a user invokes ♻️ Suggested improvement private async readStdin(): Promise<string[]> {
+ if (process.stdin.isTTY) {
+ this.error('No input piped to stdin. Use: echo "<base64>" | mplx core asset execute raw <assetId> --stdin')
+ }
+
return new Promise((resolve, reject) => {
let data = ''
process.stdin.setEncoding('utf8')
process.stdin.on('data', (chunk) => { data += chunk })
process.stdin.on('end', () => {
const lines = data.split('\n').map(l => l.trim()).filter(l => l.length > 0)
resolve(lines)
})
process.stdin.on('error', reject)
})
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async run(): Promise<unknown> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { args, flags } = await this.parse(ExecuteRaw) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { umi, explorer, chain } = this.context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let instructionData: string[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (flags.stdin) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instructionData = await this.readStdin() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (flags.instruction && flags.instruction.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instructionData = flags.instruction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.error('You must provide instructions via --instruction or --stdin') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (instructionData.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.error('No instructions provided') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const spinner = ora('Fetching asset...').start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const assetPubkey = publicKey(args.assetId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const asset = await fetchAsset(umi, assetPubkey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let collection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (asset.updateAuthority.type === 'Collection' && asset.updateAuthority.address) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| collection = await fetchCollection(umi, asset.updateAuthority.address) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [assetSignerPda] = findAssetSignerPda(umi, { asset: assetPubkey }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spinner.text = 'Deserializing instructions...' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const instructions = instructionData.map((b64, idx) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return deserializeInstruction(b64) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spinner.fail(`Failed to deserialize instruction ${idx + 1}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.error(`Failed to deserialize instruction ${idx + 1}: ${error}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spinner.text = `Executing ${instructions.length} instruction(s)...` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await execute(umi, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| asset, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| collection, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instructions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).sendAndConfirm(umi) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const signature = txSignatureToString(result.signature) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const explorerUrl = generateExplorerUrl(explorer, chain, signature, 'transaction') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spinner.succeed(`Executed ${instructions.length} instruction(s) via asset signer`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logSuccess( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `-------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Asset: ${args.assetId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Signer PDA: ${assetSignerPda.toString()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Instructions: ${instructions.length} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Signature: ${signature} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --------------------------------` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log(explorerUrl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| asset: args.assetId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signerPda: assetSignerPda.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instructionCount: instructions.length, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signature, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| explorer: explorerUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spinner.fail('Failed to execute instructions') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { fetchAsset, findAssetSignerPda } from '@metaplex-foundation/mpl-core' | ||
| import { amountToNumber, publicKey } from '@metaplex-foundation/umi' | ||
| import { Args } from '@oclif/core' | ||
| import ora from 'ora' | ||
|
|
||
| import { TransactionCommand } from '../../../../TransactionCommand.js' | ||
|
|
||
| export default class ExecuteSigner extends TransactionCommand<typeof ExecuteSigner> { | ||
| static override description = 'Show the asset signer PDA address and its SOL balance' | ||
|
|
||
| static override examples = [ | ||
| '<%= config.bin %> <%= command.id %> <assetId>', | ||
| ] | ||
|
|
||
| static override args = { | ||
| assetId: Args.string({ description: 'Asset ID to derive the signer PDA for', required: true }), | ||
| } | ||
|
|
||
| public async run(): Promise<unknown> { | ||
| const { args } = await this.parse(ExecuteSigner) | ||
| const { umi } = this.context | ||
|
|
||
| const spinner = ora('Fetching asset signer info...').start() | ||
|
|
||
| try { | ||
| const assetPubkey = publicKey(args.assetId) | ||
|
|
||
| // Verify asset exists | ||
| await fetchAsset(umi, assetPubkey) | ||
|
|
||
| const [assetSignerPda] = findAssetSignerPda(umi, { asset: assetPubkey }) | ||
| const balance = await umi.rpc.getBalance(assetSignerPda) | ||
| const balanceNumber = amountToNumber(balance) | ||
|
|
||
| spinner.succeed('Asset signer info retrieved') | ||
|
|
||
| this.logSuccess( | ||
| `-------------------------------- | ||
| Asset: ${args.assetId} | ||
| Signer PDA: ${assetSignerPda.toString()} | ||
| SOL Balance: ${balanceNumber} SOL | ||
| --------------------------------` | ||
| ) | ||
|
|
||
| return { | ||
| asset: args.assetId, | ||
| signerPda: assetSignerPda.toString(), | ||
| balance: balanceNumber, | ||
| } | ||
| } catch (error) { | ||
| spinner.fail('Failed to fetch asset signer info') | ||
| throw error | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { execute, fetchAsset, fetchCollection, findAssetSignerPda, transfer } from '@metaplex-foundation/mpl-core' | ||
| import { createNoopSigner, publicKey } from '@metaplex-foundation/umi' | ||
| import { Args, Flags } from '@oclif/core' | ||
| import ora from 'ora' | ||
|
|
||
| import { generateExplorerUrl } from '../../../../explorers.js' | ||
| import { TransactionCommand } from '../../../../TransactionCommand.js' | ||
| import { txSignatureToString } from '../../../../lib/util.js' | ||
|
|
||
| export default class ExecuteTransferAsset extends TransactionCommand<typeof ExecuteTransferAsset> { | ||
| static override description = 'Transfer a Core Asset owned by an asset\'s signer PDA to a new owner' | ||
|
|
||
| static override examples = [ | ||
| '<%= config.bin %> <%= command.id %> <assetId> --asset <targetAssetId> --new-owner <address>', | ||
| ] | ||
|
|
||
| static override args = { | ||
| assetId: Args.string({ description: 'Asset whose signer PDA owns the target asset', required: true }), | ||
| } | ||
|
|
||
| static override flags = { | ||
| asset: Flags.string({ description: 'Asset to transfer (must be owned by the signer PDA)', required: true }), | ||
| 'new-owner': Flags.string({ description: 'New owner of the target asset', required: true }), | ||
| } | ||
|
|
||
| public async run(): Promise<unknown> { | ||
| const { args, flags } = await this.parse(ExecuteTransferAsset) | ||
| const { umi, explorer, chain } = this.context | ||
|
|
||
| const spinner = ora('Fetching assets...').start() | ||
|
|
||
| try { | ||
| const signingAssetPubkey = publicKey(args.assetId) | ||
| const signingAsset = await fetchAsset(umi, signingAssetPubkey) | ||
|
|
||
| let signingCollection | ||
| if (signingAsset.updateAuthority.type === 'Collection' && signingAsset.updateAuthority.address) { | ||
| signingCollection = await fetchCollection(umi, signingAsset.updateAuthority.address) | ||
| } | ||
|
|
||
| const targetAssetPubkey = publicKey(flags.asset) | ||
| const targetAsset = await fetchAsset(umi, targetAssetPubkey) | ||
|
|
||
| let targetCollection | ||
| if (targetAsset.updateAuthority.type === 'Collection' && targetAsset.updateAuthority.address) { | ||
| targetCollection = await fetchCollection(umi, targetAsset.updateAuthority.address) | ||
| } | ||
|
|
||
| const [assetSignerPda] = findAssetSignerPda(umi, { asset: signingAssetPubkey }) | ||
|
|
||
| // Verify the target asset is owned by the signer PDA | ||
| if (targetAsset.owner.toString() !== assetSignerPda.toString()) { | ||
| spinner.fail('Transfer failed') | ||
| this.error(`Target asset is not owned by the asset signer PDA.\nExpected owner: ${assetSignerPda.toString()}\nActual owner: ${targetAsset.owner.toString()}`) | ||
| } | ||
|
|
||
| const transferIx = transfer(umi, { | ||
| asset: targetAsset, | ||
| collection: targetCollection, | ||
| newOwner: publicKey(flags['new-owner']), | ||
| authority: createNoopSigner(assetSignerPda), | ||
| }) | ||
|
|
||
| spinner.text = 'Executing asset transfer...' | ||
|
|
||
| const result = await execute(umi, { | ||
| asset: signingAsset, | ||
| collection: signingCollection, | ||
| instructions: transferIx, | ||
| }).sendAndConfirm(umi) | ||
|
|
||
| const signature = txSignatureToString(result.signature) | ||
| const explorerUrl = generateExplorerUrl(explorer, chain, signature, 'transaction') | ||
|
|
||
| spinner.succeed('Asset transferred from signer PDA') | ||
|
|
||
| this.logSuccess( | ||
| `-------------------------------- | ||
| Signing Asset: ${args.assetId} | ||
| Target Asset: ${flags.asset} | ||
| New Owner: ${flags['new-owner']} | ||
| Signature: ${signature} | ||
| --------------------------------` | ||
| ) | ||
| this.log(explorerUrl) | ||
|
|
||
| return { | ||
| signingAsset: args.assetId, | ||
| targetAsset: flags.asset, | ||
| newOwner: flags['new-owner'], | ||
| signature, | ||
| explorer: explorerUrl, | ||
| } | ||
| } catch (error) { | ||
| spinner.fail('Failed to execute asset transfer') | ||
| throw error | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { execute, fetchAsset, fetchCollection, findAssetSignerPda } from '@metaplex-foundation/mpl-core' | ||
| import { transferSol } from '@metaplex-foundation/mpl-toolbox' | ||
| import { createNoopSigner, publicKey, sol } from '@metaplex-foundation/umi' | ||
| import { Args, Flags } from '@oclif/core' | ||
| import ora from 'ora' | ||
|
|
||
| import { generateExplorerUrl } from '../../../../explorers.js' | ||
| import { TransactionCommand } from '../../../../TransactionCommand.js' | ||
| import { txSignatureToString } from '../../../../lib/util.js' | ||
|
|
||
| export default class ExecuteTransferSol extends TransactionCommand<typeof ExecuteTransferSol> { | ||
| static override description = 'Transfer SOL from an asset\'s signer PDA to a destination address' | ||
|
|
||
| static override examples = [ | ||
| '<%= config.bin %> <%= command.id %> <assetId> --amount 0.5 --destination <address>', | ||
| ] | ||
|
|
||
| static override args = { | ||
| assetId: Args.string({ description: 'Asset whose signer PDA holds the SOL', required: true }), | ||
| } | ||
|
|
||
| static override flags = { | ||
| amount: Flags.string({ description: 'Amount of SOL to transfer', required: true }), | ||
| destination: Flags.string({ description: 'Destination address', required: true }), | ||
| } | ||
|
|
||
| public async run(): Promise<unknown> { | ||
| const { args, flags } = await this.parse(ExecuteTransferSol) | ||
| const { umi, explorer, chain } = this.context | ||
|
|
||
| const amountInSol = parseFloat(flags.amount) | ||
| if (isNaN(amountInSol) || amountInSol <= 0) { | ||
| this.error('Amount must be a positive number') | ||
| } | ||
|
|
||
| const spinner = ora('Fetching asset...').start() | ||
|
|
||
| try { | ||
| const assetPubkey = publicKey(args.assetId) | ||
| const asset = await fetchAsset(umi, assetPubkey) | ||
|
|
||
| let collection | ||
| if (asset.updateAuthority.type === 'Collection' && asset.updateAuthority.address) { | ||
| collection = await fetchCollection(umi, asset.updateAuthority.address) | ||
| } | ||
|
|
||
| const [assetSignerPda] = findAssetSignerPda(umi, { asset: assetPubkey }) | ||
|
|
||
| const transferSolIx = transferSol(umi, { | ||
| source: createNoopSigner(assetSignerPda), | ||
| destination: publicKey(flags.destination), | ||
| amount: sol(amountInSol), | ||
| }) | ||
|
|
||
| spinner.text = 'Executing transfer...' | ||
|
|
||
| const result = await execute(umi, { | ||
| asset, | ||
| collection, | ||
| instructions: transferSolIx, | ||
| }).sendAndConfirm(umi) | ||
|
|
||
| const signature = txSignatureToString(result.signature) | ||
| const explorerUrl = generateExplorerUrl(explorer, chain, signature, 'transaction') | ||
|
|
||
| spinner.succeed('SOL transferred from asset signer') | ||
|
|
||
| this.logSuccess( | ||
| `-------------------------------- | ||
| Asset: ${args.assetId} | ||
| Signer PDA: ${assetSignerPda.toString()} | ||
| Amount: ${amountInSol} SOL | ||
| Destination: ${flags.destination} | ||
| Signature: ${signature} | ||
| --------------------------------` | ||
| ) | ||
| this.log(explorerUrl) | ||
|
|
||
| return { | ||
| asset: args.assetId, | ||
| signerPda: assetSignerPda.toString(), | ||
| amount: amountInSol, | ||
| destination: flags.destination, | ||
| signature, | ||
| explorer: explorerUrl, | ||
| } | ||
| } catch (error) { | ||
| spinner.fail('Failed to execute SOL transfer') | ||
| throw error | ||
| } | ||
| } | ||
| } |
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.
🧹 Nitpick | 🔵 Trivial
Unused parsed variables in parent command.
The
argsandflagsvariables are parsed but never used. For a parent command that serves as an entry point to subcommands, therun()method body can be empty or simply display help.♻️ Suggested simplification
public async run(): Promise<void> { - const {args, flags} = await this.parse(CoreAssetExecute) + await this.parse(CoreAssetExecute) + // Parent command - subcommands handle actual execution }📝 Committable suggestion
🤖 Prompt for AI Agents