diff --git a/.changeset/swift-melons-stand.md b/.changeset/swift-melons-stand.md new file mode 100644 index 00000000..e16f596d --- /dev/null +++ b/.changeset/swift-melons-stand.md @@ -0,0 +1,8 @@ +--- +'@swig-wallet/classic': minor +'@swig-wallet/coder': minor +'@swig-wallet/kit': minor +'@swig-wallet/lib': minor +--- + +Adds support for multiple SubAccount actions per role. diff --git a/bun.lock b/bun.lock index 266e59cf..430fe962 100644 --- a/bun.lock +++ b/bun.lock @@ -145,7 +145,7 @@ }, "packages/classic": { "name": "@swig-wallet/classic", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@noble/curves": "^1.8.2", "@solana-program/token": "^0.5.1", @@ -173,7 +173,7 @@ }, "packages/coder": { "name": "@swig-wallet/coder", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@solana/kit": "^2.1.0", }, @@ -191,7 +191,7 @@ }, "packages/kit": { "name": "@swig-wallet/kit", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@noble/curves": "^1.8.2", "@solana-program/token": "^0.5.1", @@ -217,7 +217,7 @@ }, "packages/lib": { "name": "@swig-wallet/lib", - "version": "1.3.0", + "version": "1.4.0", "dependencies": { "@noble/curves": "^1.8.2", "@solana-program/token": "^0.5.1", diff --git a/examples/classic/transfer/multi-subaccount-svm.ts b/examples/classic/transfer/multi-subaccount-svm.ts new file mode 100644 index 00000000..864cf9c7 --- /dev/null +++ b/examples/classic/transfer/multi-subaccount-svm.ts @@ -0,0 +1,323 @@ +import { + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SystemProgram, + Transaction, + TransactionInstruction, +} from '@solana/web3.js'; +import { + Actions, + createEd25519AuthorityInfo, + findSwigPda, + findSwigSubAccountPda, + findSwigSubAccountPdaWithIndex, + getAddAuthorityInstructions, + getCreateSubAccountInstructions, + getCreateSwigInstruction, + getSignInstructions, + getSwigCodec, + getSwigWalletAddress, + Swig, + SWIG_PROGRAM_ADDRESS, + toPublicKey, + type SwigAccount, + type SwigFetchFn, +} from '@swig-wallet/classic'; +import { FailedTransactionMetadata, LiteSVM } from 'litesvm'; +import { readFileSync } from 'node:fs'; + +// +// Helpers +// +function sendSVMTransaction( + svm: LiteSVM, + instructions: TransactionInstruction[], + payer: Keypair, +) { + svm.expireBlockhash(); + + const tx = new Transaction(); + tx.instructions = instructions; + tx.feePayer = payer.publicKey; + tx.recentBlockhash = svm.latestBlockhash(); + tx.sign(payer); + + const res = svm.sendTransaction(tx); + + if (res instanceof FailedTransactionMetadata) { + console.log('โŒ tx logs:', res.meta().logs()); + throw new Error('Transaction failed'); + } + return res; +} + +function fetchSwigAccount( + svm: LiteSVM, + swigAccountAddress: PublicKey, +): SwigAccount { + const swigAccount = svm.getAccount(swigAccountAddress); + if (!swigAccount) throw new Error('swig account not created'); + return getSwigCodec().decode(Uint8Array.from(swigAccount.data)); +} + +function fetchSwig(svm: LiteSVM, swigAccountAddress: PublicKey): Swig { + const account = fetchSwigAccount(svm, swigAccountAddress); + const swigFetchFn: SwigFetchFn = async (addr) => + fetchSwigAccount(svm, toPublicKey(addr)); + return new Swig(swigAccountAddress, account, swigFetchFn); +} + +// +// Main +// +console.log('๐Ÿš€ Starting multiple subaccounts test...\n'); +const swigProgram = Uint8Array.from(readFileSync('../../../swig.so')); +const svm = new LiteSVM(); +svm.addProgram(SWIG_PROGRAM_ADDRESS, swigProgram); + +const rootAuthority = Keypair.generate(); +svm.airdrop(rootAuthority.publicKey, BigInt(LAMPORTS_PER_SOL * 10)); +console.log('๐Ÿ‘ค Root authority:', rootAuthority.publicKey.toBase58()); + +const subAccountAuthority = Keypair.generate(); +svm.airdrop(subAccountAuthority.publicKey, BigInt(LAMPORTS_PER_SOL * 10)); +console.log( + '๐Ÿ‘ค SubAccount authority:', + subAccountAuthority.publicKey.toBase58(), +); + +const id = Uint8Array.from(Array(32).fill(3)); +const swigAccountAddress = findSwigPda(id); +console.log('๐Ÿฆ Swig account:', swigAccountAddress.toBase58()); + +// Create Swig with All permissions for root +const createSwigIx = await getCreateSwigInstruction({ + payer: rootAuthority.publicKey, + actions: Actions.set().all().get(), + authorityInfo: createEd25519AuthorityInfo(rootAuthority.publicKey), + id, +}); +sendSVMTransaction(svm, [createSwigIx], rootAuthority); + +const swig = fetchSwig(svm, swigAccountAddress); +console.log('โœ… Swig created (version:', swig.accountVersion(), ')\n'); + +const swigWalletAddress = await getSwigWalletAddress(swig); +console.log('๐Ÿ’ฐ Swig wallet:', swigWalletAddress.toBase58()); + +const rootRole = swig.roles[0]; +console.log('๐ŸŽญ Root role ID:', rootRole.id); + +// Add subaccount authority role with multiple subaccount actions (index 0, 1, 2) +console.log('\n๐Ÿ“ Adding subaccount authority role with 3 subaccount slots...'); +const addAuthorityIx = await getAddAuthorityInstructions( + swig, + rootRole.id, + createEd25519AuthorityInfo(subAccountAuthority.publicKey), + Actions.set() + .subAccount(0) // Allow index 0 + .subAccount(1) // Allow index 1 + .subAccount(2) // Allow index 2 + .manageAuthority() + .get(), +); +sendSVMTransaction(svm, addAuthorityIx, rootAuthority); + +await swig.refetch(); +const subAccountAuthRole = swig.roles[1]; +console.log('โœ… SubAccount auth role ID:', subAccountAuthRole.id); + +// ========================================== +// Test 1: Create subaccount with index 0 (legacy/default) +// ========================================== +console.log('\n๐Ÿงช TEST 1: Creating subaccount with index 0 (default)...'); +const createSubAccount0Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { subAccountIndex: 0 }, // Explicitly use index 0 +); +sendSVMTransaction(svm, createSubAccount0Ix, subAccountAuthority); + +await swig.refetch(); + +// Derive using both methods - they should match for index 0 +const subAccount0AddressLegacy = findSwigSubAccountPda( + subAccountAuthRole.swigId, + subAccountAuthRole.id, +); +const subAccount0AddressNew = findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 0, +); + +console.log( + '๐Ÿ“ SubAccount 0 (legacy PDA):', + subAccount0AddressLegacy.toBase58(), +); +console.log('๐Ÿ“ SubAccount 0 (new PDA):', subAccount0AddressNew.toBase58()); + +// Verify they match +if (subAccount0AddressLegacy.toBase58() !== subAccount0AddressNew.toBase58()) { + throw new Error( + 'โŒ Index 0 PDA derivation mismatch! Legacy and new methods should match.', + ); +} +console.log('โœ… Index 0 backwards compatibility verified!'); + +// Fund and test subaccount 0 +svm.airdrop(subAccount0AddressLegacy, BigInt(LAMPORTS_PER_SOL * 2)); +console.log( + '๐Ÿ’ฐ SubAccount 0 balance:', + svm.getBalance(subAccount0AddressLegacy), +); + +const recipient0 = Keypair.generate().publicKey; +console.log('๐Ÿ‘ค Recipient 0:', recipient0.toBase58()); +const transfer0 = SystemProgram.transfer({ + fromPubkey: subAccount0AddressLegacy, + toPubkey: recipient0, + lamports: Math.floor(0.1 * LAMPORTS_PER_SOL), +}); + +const signIx0 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer0], + true, +); +sendSVMTransaction(svm, signIx0, subAccountAuthority); + +console.log( + '๐Ÿ’ธ SubAccount 0 new balance:', + svm.getBalance(subAccount0AddressLegacy), +); +console.log('๐Ÿ’ฐ Recipient 0 balance:', svm.getBalance(recipient0)); +console.log('โœ… SubAccount 0 transfer successful!\n'); + +// ========================================== +// Test 2: Create subaccount with index 1 +// ========================================== +console.log('๐Ÿงช TEST 2: Creating subaccount with index 1...'); +const createSubAccount1Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { subAccountIndex: 1 }, +); +sendSVMTransaction(svm, createSubAccount1Ix, subAccountAuthority); + +await swig.refetch(); + +const subAccount1Address = findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 1, +); +console.log('๐Ÿ“ SubAccount 1 address:', subAccount1Address.toBase58()); + +// Verify it's different from index 0 +if (subAccount1Address.toBase58() === subAccount0AddressLegacy.toBase58()) { + throw new Error( + 'โŒ SubAccount 1 should have different address than index 0!', + ); +} +console.log('โœ… SubAccount 1 has unique address'); + +// Fund and test subaccount 1 +svm.airdrop(subAccount1Address, BigInt(LAMPORTS_PER_SOL * 2)); +console.log('๐Ÿ’ฐ SubAccount 1 balance:', svm.getBalance(subAccount1Address)); + +const recipient1 = Keypair.generate().publicKey; +console.log('๐Ÿ‘ค Recipient 1:', recipient1.toBase58()); +const transfer1 = SystemProgram.transfer({ + fromPubkey: subAccount1Address, + toPubkey: recipient1, + lamports: Math.floor(0.15 * LAMPORTS_PER_SOL), +}); + +const signIx1 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer1], + true, + { subAccountIndex: 1 }, // Use subaccount at index 1 +); +sendSVMTransaction(svm, signIx1, subAccountAuthority); + +console.log('๐Ÿ’ธ SubAccount 1 new balance:', svm.getBalance(subAccount1Address)); +console.log('๐Ÿ’ฐ Recipient 1 balance:', svm.getBalance(recipient1)); +console.log('โœ… SubAccount 1 transfer successful!\n'); + +// ========================================== +// Test 3: Create subaccount with index 2 +// ========================================== +console.log('๐Ÿงช TEST 3: Creating subaccount with index 2...'); +const createSubAccount2Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { subAccountIndex: 2 }, +); +sendSVMTransaction(svm, createSubAccount2Ix, subAccountAuthority); + +await swig.refetch(); + +const subAccount2Address = findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 2, +); +console.log('๐Ÿ“ SubAccount 2 address:', subAccount2Address.toBase58()); + +// Verify it's different from both previous subaccounts +if ( + subAccount2Address.toBase58() === subAccount0AddressLegacy.toBase58() || + subAccount2Address.toBase58() === subAccount1Address.toBase58() +) { + throw new Error('โŒ SubAccount 2 should have unique address!'); +} +console.log('โœ… SubAccount 2 has unique address'); + +// Fund and test subaccount 2 +svm.airdrop(subAccount2Address, BigInt(LAMPORTS_PER_SOL * 2)); +console.log('๐Ÿ’ฐ SubAccount 2 balance:', svm.getBalance(subAccount2Address)); + +const recipient2 = Keypair.generate().publicKey; +console.log('๐Ÿ‘ค Recipient 2:', recipient2.toBase58()); +const transfer2 = SystemProgram.transfer({ + fromPubkey: subAccount2Address, + toPubkey: recipient2, + lamports: Math.floor(0.2 * LAMPORTS_PER_SOL), +}); + +const signIx2 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer2], + true, + { subAccountIndex: 2 }, // Use subaccount at index 2 +); +sendSVMTransaction(svm, signIx2, subAccountAuthority); + +console.log('๐Ÿ’ธ SubAccount 2 new balance:', svm.getBalance(subAccount2Address)); +console.log('๐Ÿ’ฐ Recipient 2 balance:', svm.getBalance(recipient2)); +console.log('โœ… SubAccount 2 transfer successful!\n'); + +// ========================================== +// Final Summary +// ========================================== +console.log('๐Ÿ“Š FINAL SUMMARY:'); +console.log('================'); +console.log(`SubAccount 0 (index 0): ${subAccount0AddressLegacy.toBase58()}`); +console.log(` Balance: ${svm.getBalance(subAccount0AddressLegacy)}`); +console.log(` Recipient balance: ${svm.getBalance(recipient0)}`); +console.log(''); +console.log(`SubAccount 1 (index 1): ${subAccount1Address.toBase58()}`); +console.log(` Balance: ${svm.getBalance(subAccount1Address)}`); +console.log(` Recipient balance: ${svm.getBalance(recipient1)}`); +console.log(''); +console.log(`SubAccount 2 (index 2): ${subAccount2Address.toBase58()}`); +console.log(` Balance: ${svm.getBalance(subAccount2Address)}`); +console.log(` Recipient balance: ${svm.getBalance(recipient2)}`); +console.log(''); +console.log('โœ… ALL TESTS PASSED! Multiple subaccounts working correctly! ๐ŸŽ‰'); diff --git a/examples/kit/transfer/multi-subaccount-local.ts b/examples/kit/transfer/multi-subaccount-local.ts new file mode 100644 index 00000000..08ff0319 --- /dev/null +++ b/examples/kit/transfer/multi-subaccount-local.ts @@ -0,0 +1,463 @@ +import { + getTransferSolInstructionDataEncoder, + SYSTEM_PROGRAM_ADDRESS, +} from '@solana-program/system'; +import { + AccountRole, + addSignersToTransactionMessage, + appendTransactionMessageInstructions, + createSolanaRpc, + createSolanaRpcSubscriptions, + createTransactionMessage, + generateKeyPairSigner, + getSignatureFromTransaction, + lamports, + pipe, + sendAndConfirmTransactionFactory, + setTransactionMessageFeePayerSigner, + setTransactionMessageLifetimeUsingBlockhash, + signTransactionMessageWithSigners, + type Address, + type Blockhash, + type IInstruction, + type KeyPairSigner, + type Rpc, + type RpcSubscriptions, + type SolanaRpcApi, + type SolanaRpcSubscriptionsApi, +} from '@solana/kit'; +import { + Actions, + createEd25519AuthorityInfo, + fetchSwig, + findSwigPda, + findSwigSubAccountPda, + findSwigSubAccountPdaWithIndex, + getAddAuthorityInstructions, + getCreateSubAccountInstructions, + getCreateSwigInstruction, + getSignInstructions, +} from '@swig-wallet/kit'; + +function randomBytes(length: number): Uint8Array { + const arr = new Uint8Array(length); + crypto.getRandomValues(arr); + return arr; +} + +const LAMPORTS_PER_SOL = 1_000_000_000n; + +// ---------- helpers ---------- +const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +async function confirmAirdrop( + rpc: Rpc, + to: Address, + amount: bigint, +) { + const sig = await rpc.requestAirdrop(to, lamports(amount)).send(); + await rpc.getSignatureStatuses([sig]).send(); + await delay(1000); +} + +function getSolTransferInstruction(args: { + fromAddress: Address; + toAddress: Address; + lamports: bigint; +}) { + return { + programAddress: SYSTEM_PROGRAM_ADDRESS, + accounts: [ + { address: args.fromAddress, role: AccountRole.WRITABLE_SIGNER }, + { address: args.toAddress, role: AccountRole.WRITABLE }, + ], + data: new Uint8Array( + getTransferSolInstructionDataEncoder().encode({ + amount: args.lamports, + }), + ), + } satisfies IInstruction; +} + +function getTransactionMessage( + instructions: Inst, + latestBlockhash: Readonly<{ + blockhash: Blockhash; + lastValidBlockHeight: bigint; + }>, + feePayer: KeyPairSigner, + signers: KeyPairSigner[] = [], +) { + return pipe( + createTransactionMessage({ version: 0 }), + (tx) => setTransactionMessageFeePayerSigner(feePayer, tx), + (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), + (tx) => appendTransactionMessageInstructions(instructions, tx), + (tx) => addSignersToTransactionMessage(signers, tx), + ); +} + +async function sendTransaction( + connection: { + rpc: Rpc; + rpcSubscriptions: RpcSubscriptions; + }, + instructions: T, + payer: KeyPairSigner, + signers: KeyPairSigner[] = [], +) { + const { value: latestBlockhash } = await connection.rpc + .getLatestBlockhash() + .send(); + + const txMsg = getTransactionMessage( + instructions, + latestBlockhash, + payer, + signers, + ); + const signed = await signTransactionMessageWithSigners(txMsg); + + await sendAndConfirmTransactionFactory(connection)(signed, { + commitment: 'confirmed', + }); + + return getSignatureFromTransaction(signed).toString(); +} + +// ---------- main ---------- +console.log('๐Ÿš€ Starting multiple subaccounts test (Kit)...\n'); + +const connection = { + rpc: createSolanaRpc('http://localhost:8899'), + rpcSubscriptions: createSolanaRpcSubscriptions('ws://localhost:8900'), +}; + +// Root authority +const rootAuthority = await generateKeyPairSigner(); +await confirmAirdrop( + connection.rpc, + rootAuthority.address, + 10n * LAMPORTS_PER_SOL, +); +console.log('๐Ÿ‘ค Root authority:', rootAuthority.address); + +// Sub-account authority manager +const subAccountAuthority = await generateKeyPairSigner(); +await confirmAirdrop( + connection.rpc, + subAccountAuthority.address, + 10n * LAMPORTS_PER_SOL, +); +console.log('๐Ÿ‘ค SubAccount authority:', subAccountAuthority.address); + +const id = randomBytes(32); +const swigAccountAddress = await findSwigPda(id); +console.log('๐Ÿฆ Swig address:', swigAccountAddress); + +// Create SWIG (root has all actions) +const createSwigIx = await getCreateSwigInstruction({ + payer: rootAuthority.address, + actions: Actions.set().all().get(), + authorityInfo: createEd25519AuthorityInfo(rootAuthority.address), + id, +}); +await sendTransaction(connection, [createSwigIx], rootAuthority); + +const swig = await fetchSwig(connection.rpc, swigAccountAddress); +console.log('โœ… Swig created (version: v2)\n'); + +// Resolve root role by signer +const rootRole = swig.findRolesByEd25519SignerPk(rootAuthority.address)[0]; +if (!rootRole) throw new Error('Root role not found'); +console.log('๐ŸŽญ Root role ID:', rootRole.id); + +// Add an authority that can manage sub-accounts with multiple subaccount slots (index 0, 1, 2) +console.log('\n๐Ÿ“ Adding subaccount authority role with 3 subaccount slots...'); +const addAuthorityIxs = await getAddAuthorityInstructions( + swig, + rootRole.id, + createEd25519AuthorityInfo(subAccountAuthority.address), + Actions.set() + .subAccount(0) // Allow index 0 + .subAccount(1) // Allow index 1 + .subAccount(2) // Allow index 2 + .manageAuthority() + .get(), + { payer: rootAuthority.address }, +); +await sendTransaction(connection, addAuthorityIxs, rootAuthority); + +// Refetch to see the new role +await swig.refetch(); + +let subAccountAuthRole = swig.findRolesByEd25519SignerPk( + subAccountAuthority.address, +)[0]; +if (!subAccountAuthRole) + throw new Error('Sub-account authority role not found'); +console.log('โœ… SubAccount auth role ID:', subAccountAuthRole.id); + +// ========================================== +// Test 1: Create subaccount with index 0 (legacy/default) +// ========================================== +console.log('\n๐Ÿงช TEST 1: Creating subaccount with index 0 (default)...'); +const createSubAccount0Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { + payer: subAccountAuthority.address, + subAccountIndex: 0, // Explicitly use index 0 + }, +); +await sendTransaction(connection, createSubAccount0Ix, subAccountAuthority); + +await swig.refetch(); +subAccountAuthRole = swig.findRolesByEd25519SignerPk( + subAccountAuthority.address, +)[0]!; + +// Derive using both methods - they should match for index 0 +const subAccount0AddressLegacy = await findSwigSubAccountPda( + subAccountAuthRole.swigId, + subAccountAuthRole.id, +); +const subAccount0AddressNew = await findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 0, +); + +console.log('๐Ÿ“ SubAccount 0 (legacy PDA):', subAccount0AddressLegacy); +console.log('๐Ÿ“ SubAccount 0 (new PDA):', subAccount0AddressNew); + +// Verify they match +if (subAccount0AddressLegacy !== subAccount0AddressNew) { + throw new Error( + 'โŒ Index 0 PDA derivation mismatch! Legacy and new methods should match.', + ); +} +console.log('โœ… Index 0 backwards compatibility verified!'); + +// Fund sub-account +await confirmAirdrop( + connection.rpc, + subAccount0AddressLegacy, + 2n * LAMPORTS_PER_SOL, +); + +const subBalance0 = ( + await connection.rpc.getBalance(subAccount0AddressLegacy).send() +).value; +console.log('๐Ÿ’ฐ SubAccount 0 balance:', subBalance0.toString()); + +// Prepare a transfer from the sub-account +const recipient0 = (await generateKeyPairSigner()).address; +console.log('๐Ÿ‘ค Recipient 0:', recipient0); +const transfer0 = getSolTransferInstruction({ + lamports: LAMPORTS_PER_SOL / 10n, // 0.1 SOL + toAddress: recipient0, + fromAddress: subAccount0AddressLegacy, +}); + +// Fresh finalized slot +const signSlot0 = BigInt( + await connection.rpc.getSlot({ commitment: 'finalized' }).send(), +); + +// Sign (from sub-account) and send +const signIx0 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer0], + true, + { payer: subAccountAuthority.address, currentSlot: signSlot0 }, +); + +await sendTransaction(connection, signIx0, subAccountAuthority); + +const newSubBalance0 = ( + await connection.rpc.getBalance(subAccount0AddressLegacy).send() +).value; +console.log('๐Ÿ’ธ SubAccount 0 new balance:', newSubBalance0.toString()); + +const recipientBalance0 = (await connection.rpc.getBalance(recipient0).send()) + .value; +console.log('๐Ÿ’ฐ Recipient 0 balance:', recipientBalance0.toString()); +console.log('โœ… SubAccount 0 transfer successful!\n'); + +// ========================================== +// Test 2: Create subaccount with index 1 +// ========================================== +console.log('๐Ÿงช TEST 2: Creating subaccount with index 1...'); +const createSubAccount1Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { + payer: subAccountAuthority.address, + subAccountIndex: 1, + }, +); +await sendTransaction(connection, createSubAccount1Ix, subAccountAuthority); + +await swig.refetch(); +subAccountAuthRole = swig.findRolesByEd25519SignerPk( + subAccountAuthority.address, +)[0]!; + +const subAccount1Address = await findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 1, +); +console.log('๐Ÿ“ SubAccount 1 address:', subAccount1Address); + +// Verify it's different from index 0 +if (subAccount1Address === subAccount0AddressLegacy) { + throw new Error( + 'โŒ SubAccount 1 should have different address than index 0!', + ); +} +console.log('โœ… SubAccount 1 has unique address'); + +// Fund sub-account +await confirmAirdrop(connection.rpc, subAccount1Address, 2n * LAMPORTS_PER_SOL); + +const subBalance1 = (await connection.rpc.getBalance(subAccount1Address).send()) + .value; +console.log('๐Ÿ’ฐ SubAccount 1 balance:', subBalance1.toString()); + +// Prepare a transfer from the sub-account +const recipient1 = (await generateKeyPairSigner()).address; +console.log('๐Ÿ‘ค Recipient 1:', recipient1); +const transfer1 = getSolTransferInstruction({ + lamports: (LAMPORTS_PER_SOL * 15n) / 100n, // 0.15 SOL + toAddress: recipient1, + fromAddress: subAccount1Address, +}); + +const signSlot1 = BigInt( + await connection.rpc.getSlot({ commitment: 'finalized' }).send(), +); + +const signIx1 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer1], + true, + { + payer: subAccountAuthority.address, + currentSlot: signSlot1, + subAccountIndex: 1, + }, +); + +await sendTransaction(connection, signIx1, subAccountAuthority); + +const newSubBalance1 = ( + await connection.rpc.getBalance(subAccount1Address).send() +).value; +console.log('๐Ÿ’ธ SubAccount 1 new balance:', newSubBalance1.toString()); + +const recipientBalance1 = (await connection.rpc.getBalance(recipient1).send()) + .value; +console.log('๐Ÿ’ฐ Recipient 1 balance:', recipientBalance1.toString()); +console.log('โœ… SubAccount 1 transfer successful!\n'); + +// ========================================== +// Test 3: Create subaccount with index 2 +// ========================================== +console.log('๐Ÿงช TEST 3: Creating subaccount with index 2...'); +const createSubAccount2Ix = await getCreateSubAccountInstructions( + swig, + subAccountAuthRole.id, + { + payer: subAccountAuthority.address, + subAccountIndex: 2, + }, +); +await sendTransaction(connection, createSubAccount2Ix, subAccountAuthority); + +await swig.refetch(); +subAccountAuthRole = swig.findRolesByEd25519SignerPk( + subAccountAuthority.address, +)[0]!; + +const subAccount2Address = await findSwigSubAccountPdaWithIndex( + subAccountAuthRole.swigId, + subAccountAuthRole.id, + 2, +); +console.log('๐Ÿ“ SubAccount 2 address:', subAccount2Address); + +// Verify it's different from both previous subaccounts +if ( + subAccount2Address === subAccount0AddressLegacy || + subAccount2Address === subAccount1Address +) { + throw new Error('โŒ SubAccount 2 should have unique address!'); +} +console.log('โœ… SubAccount 2 has unique address'); + +// Fund sub-account +await confirmAirdrop(connection.rpc, subAccount2Address, 2n * LAMPORTS_PER_SOL); + +const subBalance2 = (await connection.rpc.getBalance(subAccount2Address).send()) + .value; +console.log('๐Ÿ’ฐ SubAccount 2 balance:', subBalance2.toString()); + +// Prepare a transfer from the sub-account +const recipient2 = (await generateKeyPairSigner()).address; +console.log('๐Ÿ‘ค Recipient 2:', recipient2); +const transfer2 = getSolTransferInstruction({ + lamports: LAMPORTS_PER_SOL / 5n, // 0.2 SOL + toAddress: recipient2, + fromAddress: subAccount2Address, +}); + +const signSlot2 = BigInt( + await connection.rpc.getSlot({ commitment: 'finalized' }).send(), +); + +const signIx2 = await getSignInstructions( + swig, + subAccountAuthRole.id, + [transfer2], + true, + { + payer: subAccountAuthority.address, + currentSlot: signSlot2, + subAccountIndex: 2, + }, +); + +await sendTransaction(connection, signIx2, subAccountAuthority); + +const newSubBalance2 = ( + await connection.rpc.getBalance(subAccount2Address).send() +).value; +console.log('๐Ÿ’ธ SubAccount 2 new balance:', newSubBalance2.toString()); + +const recipientBalance2 = (await connection.rpc.getBalance(recipient2).send()) + .value; +console.log('๐Ÿ’ฐ Recipient 2 balance:', recipientBalance2.toString()); +console.log('โœ… SubAccount 2 transfer successful!\n'); + +// ========================================== +// Final Summary +// ========================================== +console.log('๐Ÿ“Š FINAL SUMMARY:'); +console.log('================'); +console.log(`SubAccount 0 (index 0): ${subAccount0AddressLegacy}`); +console.log(` Balance: ${newSubBalance0.toString()}`); +console.log(` Recipient balance: ${recipientBalance0.toString()}`); +console.log(''); +console.log(`SubAccount 1 (index 1): ${subAccount1Address}`); +console.log(` Balance: ${newSubBalance1.toString()}`); +console.log(` Recipient balance: ${recipientBalance1.toString()}`); +console.log(''); +console.log(`SubAccount 2 (index 2): ${subAccount2Address}`); +console.log(` Balance: ${newSubBalance2.toString()}`); +console.log(` Recipient balance: ${recipientBalance2.toString()}`); +console.log(''); +console.log('โœ… ALL TESTS PASSED! Multiple subaccounts working correctly! ๐ŸŽ‰'); diff --git a/packages/classic/src/accounts/swig.ts b/packages/classic/src/accounts/swig.ts index 93d40e09..857cbec4 100644 --- a/packages/classic/src/accounts/swig.ts +++ b/packages/classic/src/accounts/swig.ts @@ -132,6 +132,49 @@ export function findSwigSubAccountPda( )[0]; } +/** + * Utility for deriving a Swig SubAccount PDA with index + * Index 0 uses legacy 3-seed derivation for backwards compatibility + * Indices 1-254 use new 4-seed derivation with index + * @param swigId Swig ID + * @param roleId Role ID + * @param subAccountIndex Sub-account index (0-254, defaults to 0) + * @returns PublicKey + */ +export function findSwigSubAccountPdaWithIndex( + swigId: Uint8Array, + roleId: number, + subAccountIndex: number = 0, +): PublicKey { + if (subAccountIndex < 0 || subAccountIndex > 254) { + throw new Error('Sub-account index must be between 0 and 254'); + } + + const roleIdU32 = new Uint8Array(4); + const view = new DataView(roleIdU32.buffer); + view.setUint32(0, roleId, true); + + if (subAccountIndex === 0) { + // Legacy derivation for index 0 (backwards compatible) + return PublicKey.findProgramAddressSync( + [Buffer.from('sub-account'), Buffer.from(swigId), Buffer.from(roleIdU32)], + SWIG_PROGRAM_ADDRESS, + )[0]; + } else { + // New derivation for index 1-254 with index in seeds + const indexU8 = new Uint8Array([subAccountIndex]); + return PublicKey.findProgramAddressSync( + [ + Buffer.from('sub-account'), + Buffer.from(swigId), + Buffer.from(roleIdU32), + Buffer.from(indexU8), + ], + SWIG_PROGRAM_ADDRESS, + )[0]; + } +} + export function getSwigAccountAddress(swig: Swig): PublicKey { const publicKeyBytes = getSwigAccountAddress(swig).toBytes(); return new PublicKey(publicKeyBytes); diff --git a/packages/coder/src/errors/index.ts b/packages/coder/src/errors/index.ts index 61c564c7..4658c0cc 100644 --- a/packages/coder/src/errors/index.ts +++ b/packages/coder/src/errors/index.ts @@ -1 +1 @@ -// export * from './swig'; +export * from './swig'; diff --git a/packages/coder/src/errors/swig.ts b/packages/coder/src/errors/swig.ts index 0ee850a5..fa71447e 100644 --- a/packages/coder/src/errors/swig.ts +++ b/packages/coder/src/errors/swig.ts @@ -1,88 +1,414 @@ -/** OwnerMismatch: Account {0} Owner mismatch */ -const SWIG_ERROR__OWNER_MISMATCH = 0x0; // 0 -/** AccountNotEmpty: Account {0} is not empty */ -const SWIG_ERROR__ACCOUNT_NOT_EMPTY = 0x1; // 1 -/** NotOnCurve: Account {0} must be on curve */ -const SWIG_ERROR__NOT_ON_CURVE = 0x2; // 2 -/** ExpectedSigner: Account {0} must be a signer */ -const SWIG_ERROR__EXPECTED_SIGNER = 0x3; // 3 -/** StateError: State Error: {0} */ -const SWIG_ERROR__STATE_ERROR = 0x4; // 4 -/** AccountBorrowFailed: Account {0} borrow failed */ -const SWIG_ERROR__ACCOUNT_BORROW_FAILED = 0x5; // 5 -/** InvalidAuthorityType: Invalid Authority Type */ -const SWIG_ERROR__INVALID_AUTHORITY_TYPE = 0x6; // 6 -/** Cpi: Call from CPI not allowed */ -const SWIG_ERROR__CPI = 0x7; // 7 -/** InvalidSeed: Account {0} Invalid Seeds */ -const SWIG_ERROR__INVALID_SEED = 0x8; // 8 -/** MissingInstructions: Missing Instructions */ -const SWIG_ERROR__MISSING_INSTRUCTIONS = 0x9; // 9 -/** InvalidAuthorityPayload: Invalid Authority Payload */ -const SWIG_ERROR__INVALID_AUTHORITY_PAYLOAD = 0xa; // 10 -/** InvalidAuthority: Invalid Authority */ -const SWIG_ERROR__INVALID_AUTHORITY = 0xb; // 11 -/** InstructionError: Instruction Error: {0} */ -const SWIG_ERROR__INSTRUCTION_ERROR = 0xc; // 12 -/** SerializationError: Serialization Error */ -const SWIG_ERROR__SERIALIZATION_ERROR = 0xd; // 13 -/** InvalidAccounts: Invalid Accounts {0} */ -const SWIG_ERROR__INVALID_ACCOUNTS = 0xe; // 14 -/** PermissionDenied: Permission Denied {0} */ -const SWIG_ERROR__PERMISSION_DENIED = 0xf; // 15 -/** InvalidSystemProgram: Invalid System Program */ -const SWIG_ERROR__INVALID_SYSTEM_PROGRAM = 0x10; // 16 -/** DuplicateAuthority: Invalid Authority */ -const SWIG_ERROR__DUPLICATE_AUTHORITY = 0x11; // 17 -/** InvalidOperation: Invalid Operation {0} */ -const SWIG_ERROR__INVALID_OPERATION = 0x12; // 18 +// ============================================================================ +// SwigError (0-49): Core program errors from program/src/error.rs +// ============================================================================ + +/** InvalidSwigAccountDiscriminator: Invalid discriminator in Swig account data */ +const SWIG_ERROR__INVALID_SWIG_ACCOUNT_DISCRIMINATOR = 0x0; // 0 +/** OwnerMismatchSwigAccount: Swig account owner does not match expected value */ +const SWIG_ERROR__OWNER_MISMATCH_SWIG_ACCOUNT = 0x1; // 1 +/** AccountNotEmptySwigAccount: Swig account is not empty when it should be */ +const SWIG_ERROR__ACCOUNT_NOT_EMPTY_SWIG_ACCOUNT = 0x2; // 2 +/** NotOnCurveSwigAccount: Public key in Swig account is not on the curve */ +const SWIG_ERROR__NOT_ON_CURVE_SWIG_ACCOUNT = 0x3; // 3 +/** ExpectedSignerSwigAccount: Expected Swig account to be a signer but it isn't */ +const SWIG_ERROR__EXPECTED_SIGNER_SWIG_ACCOUNT = 0x4; // 4 +/** StateError: General state error in program execution */ +const SWIG_ERROR__STATE_ERROR = 0x5; // 5 +/** AccountBorrowFailed: Failed to borrow account data */ +const SWIG_ERROR__ACCOUNT_BORROW_FAILED = 0x6; // 6 +/** InvalidAuthorityType: Invalid authority type specified */ +const SWIG_ERROR__INVALID_AUTHORITY_TYPE = 0x7; // 7 +/** Cpi: Error during cross-program invocation */ +const SWIG_ERROR__CPI = 0x8; // 8 +/** InvalidSeedSwigAccount: Invalid seed used for Swig account derivation */ +const SWIG_ERROR__INVALID_SEED_SWIG_ACCOUNT = 0x9; // 9 +/** MissingInstructions: Required instructions are missing */ +const SWIG_ERROR__MISSING_INSTRUCTIONS = 0xa; // 10 +/** InvalidAuthorityPayload: Invalid authority payload format */ +const SWIG_ERROR__INVALID_AUTHORITY_PAYLOAD = 0xb; // 11 +/** InvalidAuthorityNotFoundByRoleId: Authority not found for given role ID */ +const SWIG_ERROR__INVALID_AUTHORITY_NOT_FOUND_BY_ROLE_ID = 0xc; // 12 +/** InvalidAuthorityMustHaveAtLeastOneAction: Authority must have at least one action */ +const SWIG_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION = 0xd; // 13 +/** InstructionExecutionError: Error during instruction execution */ +const SWIG_ERROR__INSTRUCTION_EXECUTION_ERROR = 0xe; // 14 +/** SerializationError: Error during data serialization */ +const SWIG_ERROR__SERIALIZATION_ERROR = 0xf; // 15 +/** InvalidSwigSignInstructionDataTooShort: Sign instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_SIGN_INSTRUCTION_DATA_TOO_SHORT = 0x10; // 16 +/** InvalidSwigRemoveAuthorityInstructionDataTooShort: Remove authority instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_REMOVE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT = 0x11; // 17 +/** InvalidSwigAddAuthorityInstructionDataTooShort: Add authority instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_ADD_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT = 0x12; // 18 +/** InvalidSwigUpdateAuthorityInstructionDataTooShort: Update authority instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_UPDATE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT = 0x13; // 19 +/** InvalidSwigCreateInstructionDataTooShort: Create instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_CREATE_INSTRUCTION_DATA_TOO_SHORT = 0x14; // 20 +/** InvalidSwigCreateSessionInstructionDataTooShort: Create session instruction data is too short */ +const SWIG_ERROR__INVALID_SWIG_CREATE_SESSION_INSTRUCTION_DATA_TOO_SHORT = 0x15; // 21 +/** InvalidAccountsLength: Invalid number of accounts provided */ +const SWIG_ERROR__INVALID_ACCOUNTS_LENGTH = 0x16; // 22 +/** InvalidAccountsSwigMustBeFirst: Swig account must be the first account in the list */ +const SWIG_ERROR__INVALID_ACCOUNTS_SWIG_MUST_BE_FIRST = 0x17; // 23 +/** InvalidSystemProgram: Invalid system program account */ +const SWIG_ERROR__INVALID_SYSTEM_PROGRAM = 0x18; // 24 +/** DuplicateAuthority: Authority already exists */ +const SWIG_ERROR__DUPLICATE_AUTHORITY = 0x19; // 25 +/** InvalidOperation: Invalid operation attempted */ +const SWIG_ERROR__INVALID_OPERATION = 0x1a; // 26 +/** InvalidAlignment: Data alignment error */ +const SWIG_ERROR__INVALID_ALIGNMENT = 0x1b; // 27 +/** InvalidSeedSubAccount: Invalid seed used for sub-account derivation */ +const SWIG_ERROR__INVALID_SEED_SUB_ACCOUNT = 0x1c; // 28 +/** InsufficientFunds: Insufficient funds for operation */ +const SWIG_ERROR__INSUFFICIENT_FUNDS = 0x1d; // 29 +/** OwnerMismatchTokenAccount: Token account owner mismatch */ +const SWIG_ERROR__OWNER_MISMATCH_TOKEN_ACCOUNT = 0x1e; // 30 +/** PermissionDenied: Permission denied for operation */ +const SWIG_ERROR__PERMISSION_DENIED = 0x1f; // 31 +/** InvalidSignature: Invalid signature provided */ +const SWIG_ERROR__INVALID_SIGNATURE = 0x20; // 32 +/** InvalidInstructionDataTooShort: Instruction data is too short */ +const SWIG_ERROR__INVALID_INSTRUCTION_DATA_TOO_SHORT = 0x21; // 33 +/** OwnerMismatchSubAccount: Sub-account owner mismatch */ +const SWIG_ERROR__OWNER_MISMATCH_SUB_ACCOUNT = 0x22; // 34 +/** SubAccountAlreadyExists: Sub-account already exists */ +const SWIG_ERROR__SUB_ACCOUNT_ALREADY_EXISTS = 0x23; // 35 +/** AuthorityCannotCreateSubAccount: Authority cannot create sub-account */ +const SWIG_ERROR__AUTHORITY_CANNOT_CREATE_SUB_ACCOUNT = 0x24; // 36 +/** InvalidSwigSubAccountDiscriminator: Invalid discriminator in sub-account data */ +const SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISCRIMINATOR = 0x25; // 37 +/** InvalidSwigSubAccountDisabled: Sub-account is disabled */ +const SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISABLED = 0x26; // 38 +/** InvalidSwigSubAccountSwigIdMismatch: Sub-account Swig ID mismatch */ +const SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_SWIG_ID_MISMATCH = 0x27; // 39 +/** InvalidSwigSubAccountRoleIdMismatch: Sub-account role ID mismatch */ +const SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_ROLE_ID_MISMATCH = 0x28; // 40 +/** InvalidSwigTokenAccountOwner: Invalid token account owner */ +const SWIG_ERROR__INVALID_SWIG_TOKEN_ACCOUNT_OWNER = 0x29; // 41 +/** InvalidProgramScopeBalanceFields: Invalid program scope balance field configuration */ +const SWIG_ERROR__INVALID_PROGRAM_SCOPE_BALANCE_FIELDS = 0x2a; // 42 +/** AccountDataModifiedUnexpectedly: Account data was modified in unexpected ways during instruction execution */ +const SWIG_ERROR__ACCOUNT_DATA_MODIFIED_UNEXPECTEDLY = 0x2b; // 43 +/** PermissionDeniedCannotUpdateRootAuthority: Cannot update root authority (ID 0) */ +const SWIG_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY = 0x2c; // 44 +/** SignV1CannotBeUsedWithSwigV2: SignV1 instruction cannot be used with Swig v2 accounts */ +const SWIG_ERROR__SIGN_V1_CANNOT_BE_USED_WITH_SWIG_V2 = 0x2d; // 45 +/** SignV2CannotBeUsedWithSwigV1: SignV2 instruction cannot be used with Swig v1 accounts */ +const SWIG_ERROR__SIGN_V2_CANNOT_BE_USED_WITH_SWIG_V1 = 0x2e; // 46 +/** InvalidSubAccountIndex: Invalid sub-account index (must be 0-254) */ +const SWIG_ERROR__INVALID_SUB_ACCOUNT_INDEX = 0x2f; // 47 +/** SubAccountIndexMismatch: Sub-account index mismatch between action and instruction */ +const SWIG_ERROR__SUB_ACCOUNT_INDEX_MISMATCH = 0x30; // 48 +/** SubAccountActionNotFound: Sub-account action not found for the specified index */ +const SWIG_ERROR__SUB_ACCOUNT_ACTION_NOT_FOUND = 0x31; // 49 + +// ============================================================================ +// SwigStateError (1000-1007): State management errors from state/src/lib.rs +// ============================================================================ + +/** InvalidAccountData: Account data is invalid or corrupted */ +const SWIG_STATE_ERROR__INVALID_ACCOUNT_DATA = 0x3e8; // 1000 +/** InvalidActionData: Action data is invalid or malformed */ +const SWIG_STATE_ERROR__INVALID_ACTION_DATA = 0x3e9; // 1001 +/** InvalidAuthorityData: Authority data is invalid or malformed */ +const SWIG_STATE_ERROR__INVALID_AUTHORITY_DATA = 0x3ea; // 1002 +/** InvalidRoleData: Role data is invalid or malformed */ +const SWIG_STATE_ERROR__INVALID_ROLE_DATA = 0x3eb; // 1003 +/** InvalidSwigData: Swig account data is invalid or malformed */ +const SWIG_STATE_ERROR__INVALID_SWIG_DATA = 0x3ec; // 1004 +/** RoleNotFound: Specified role could not be found */ +const SWIG_STATE_ERROR__ROLE_NOT_FOUND = 0x3ed; // 1005 +/** PermissionLoadError: Error loading permissions */ +const SWIG_STATE_ERROR__PERMISSION_LOAD_ERROR = 0x3ee; // 1006 +/** InvalidAuthorityMustHaveAtLeastOneAction: Adding an authority requires at least one action */ +const SWIG_STATE_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION = 0x3ef; // 1007 + +// ============================================================================ +// SwigAuthenticateError (3000-3032): Authentication errors from state/src/lib.rs +// ============================================================================ + +/** InvalidAuthority: Invalid authority provided */ +const SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY = 0xbb8; // 3000 +/** InvalidAuthorityPayload: Invalid authority payload format */ +const SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_PAYLOAD = 0xbb9; // 3001 +/** InvalidDataPayload: Invalid data payload format */ +const SWIG_AUTHENTICATE_ERROR__INVALID_DATA_PAYLOAD = 0xbba; // 3002 +/** InvalidAuthorityEd25519MissingAuthorityAccount: Missing Ed25519 authority account */ +const SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_ED25519_MISSING_AUTHORITY_ACCOUNT = 0xbbb; // 3003 +/** AuthorityDoesNotSupportSessionBasedAuth: Authority does not support session-based authentication */ +const SWIG_AUTHENTICATE_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION_BASED_AUTH = 0xbbc; // 3004 +/** PermissionDenied: Generic permission denied error */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED = 0xbbd; // 3005 +/** PermissionDeniedMissingPermission: Missing required permission */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_MISSING_PERMISSION = 0xbbe; // 3006 +/** PermissionDeniedTokenAccountPermissionFailure: Token account permission check failed */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_PERMISSION_FAILURE = 0xbbf; // 3007 +/** PermissionDeniedTokenAccountDelegatePresent: Token account has an active delegate or close authority */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_DELEGATE_PRESENT = 0xbc0; // 3008 +/** PermissionDeniedTokenAccountNotInitialized: Token account is not initialized */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_NOT_INITIALIZED = 0xbc1; // 3009 +/** PermissionDeniedToManageAuthority: No permission to manage authority */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TO_MANAGE_AUTHORITY = 0xbc2; // 3010 +/** PermissionDeniedInsufficientBalance: Insufficient balance for operation */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_INSUFFICIENT_BALANCE = 0xbc3; // 3011 +/** PermissionDeniedCannotRemoveRootAuthority: Cannot remove root authority */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_REMOVE_ROOT_AUTHORITY = 0xbc4; // 3012 +/** PermissionDeniedCannotUpdateRootAuthority: Cannot update root authority */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY = 0xbc5; // 3013 +/** PermissionDeniedSessionExpired: Session has expired */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SESSION_EXPIRED = 0xbc6; // 3014 +/** PermissionDeniedSecp256k1InvalidSignature: Invalid Secp256k1 signature */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE = 0xbc7; // 3015 +/** PermissionDeniedSecp256k1InvalidSignatureAge: Secp256k1 signature age is invalid */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE_AGE = 0xbc8; // 3016 +/** PermissionDeniedSecp256k1SignatureReused: Secp256k1 signature has been reused */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_SIGNATURE_REUSED = 0xbc9; // 3017 +/** PermissionDeniedSecp256k1InvalidHash: Invalid Secp256k1 hash */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_HASH = 0xbca; // 3018 +/** PermissionDeniedSecp256r1SignatureReused: Secp256r1 signature has been reused */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_SIGNATURE_REUSED = 0xbcb; // 3019 +/** PermissionDeniedStakeAccountInvalidState: Stake account is in an invalid state */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_STAKE_ACCOUNT_INVALID_STATE = 0xbcc; // 3020 +/** InvalidSessionKeyCannotReuseSessionKey: Cannot reuse session key */ +const SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_KEY_CANNOT_REUSE_SESSION_KEY = 0xbcd; // 3021 +/** InvalidSessionDuration: Invalid session duration */ +const SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_DURATION = 0xbce; // 3022 +/** PermissionDeniedTokenAccountAuthorityNotSwig: Token account authority is not the Swig account */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_AUTHORITY_NOT_SWIG = 0xbcf; // 3023 +/** PermissionDeniedSecp256r1InvalidInstruction: Invalid Secp256r1 instruction */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_INSTRUCTION = 0xbd0; // 3024 +/** PermissionDeniedSecp256r1InvalidPubkey: Invalid Secp256r1 public key */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_PUBKEY = 0xbd1; // 3025 +/** PermissionDeniedSecp256r1InvalidMessageHash: Invalid Secp256r1 message hash */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE_HASH = 0xbd2; // 3026 +/** PermissionDeniedSecp256r1InvalidMessage: Invalid Secp256r1 message */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE = 0xbd3; // 3027 +/** PermissionDeniedSecp256r1InvalidAuthenticationKind: Invalid Secp256r1 authentication kind */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_AUTHENTICATION_KIND = 0xbd4; // 3028 +/** PermissionDeniedSolDestinationLimitExceeded: SOL destination limit exceeded */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_LIMIT_EXCEEDED = 0xbd5; // 3029 +/** PermissionDeniedSolDestinationRecurringLimitExceeded: SOL destination recurring limit exceeded */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_RECURRING_LIMIT_EXCEEDED = 0xbd6; // 3030 +/** PermissionDeniedTokenDestinationLimitExceeded: Token destination limit exceeded */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_DESTINATION_LIMIT_EXCEEDED = 0xbd7; // 3031 +/** PermissionDeniedRecurringTokenDestinationLimitExceeded: Token destination recurring limit exceeded */ +const SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_RECURRING_TOKEN_DESTINATION_LIMIT_EXCEEDED = 0xbd8; // 3032 + +// ============================================================================ +// Combined Error Type +// ============================================================================ export type SwigError = + // SwigError (0-49) + | typeof SWIG_ERROR__INVALID_SWIG_ACCOUNT_DISCRIMINATOR + | typeof SWIG_ERROR__OWNER_MISMATCH_SWIG_ACCOUNT + | typeof SWIG_ERROR__ACCOUNT_NOT_EMPTY_SWIG_ACCOUNT + | typeof SWIG_ERROR__NOT_ON_CURVE_SWIG_ACCOUNT + | typeof SWIG_ERROR__EXPECTED_SIGNER_SWIG_ACCOUNT + | typeof SWIG_ERROR__STATE_ERROR | typeof SWIG_ERROR__ACCOUNT_BORROW_FAILED - | typeof SWIG_ERROR__ACCOUNT_NOT_EMPTY + | typeof SWIG_ERROR__INVALID_AUTHORITY_TYPE | typeof SWIG_ERROR__CPI - | typeof SWIG_ERROR__DUPLICATE_AUTHORITY - | typeof SWIG_ERROR__EXPECTED_SIGNER - | typeof SWIG_ERROR__INSTRUCTION_ERROR - | typeof SWIG_ERROR__INVALID_ACCOUNTS - | typeof SWIG_ERROR__INVALID_AUTHORITY + | typeof SWIG_ERROR__INVALID_SEED_SWIG_ACCOUNT + | typeof SWIG_ERROR__MISSING_INSTRUCTIONS | typeof SWIG_ERROR__INVALID_AUTHORITY_PAYLOAD - | typeof SWIG_ERROR__INVALID_AUTHORITY_TYPE - | typeof SWIG_ERROR__INVALID_OPERATION - | typeof SWIG_ERROR__INVALID_SEED + | typeof SWIG_ERROR__INVALID_AUTHORITY_NOT_FOUND_BY_ROLE_ID + | typeof SWIG_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION + | typeof SWIG_ERROR__INSTRUCTION_EXECUTION_ERROR + | typeof SWIG_ERROR__SERIALIZATION_ERROR + | typeof SWIG_ERROR__INVALID_SWIG_SIGN_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_SWIG_REMOVE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_SWIG_ADD_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_SWIG_UPDATE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_SWIG_CREATE_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_SWIG_CREATE_SESSION_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__INVALID_ACCOUNTS_LENGTH + | typeof SWIG_ERROR__INVALID_ACCOUNTS_SWIG_MUST_BE_FIRST | typeof SWIG_ERROR__INVALID_SYSTEM_PROGRAM - | typeof SWIG_ERROR__MISSING_INSTRUCTIONS - | typeof SWIG_ERROR__NOT_ON_CURVE - | typeof SWIG_ERROR__OWNER_MISMATCH + | typeof SWIG_ERROR__DUPLICATE_AUTHORITY + | typeof SWIG_ERROR__INVALID_OPERATION + | typeof SWIG_ERROR__INVALID_ALIGNMENT + | typeof SWIG_ERROR__INVALID_SEED_SUB_ACCOUNT + | typeof SWIG_ERROR__INSUFFICIENT_FUNDS + | typeof SWIG_ERROR__OWNER_MISMATCH_TOKEN_ACCOUNT | typeof SWIG_ERROR__PERMISSION_DENIED - | typeof SWIG_ERROR__SERIALIZATION_ERROR - | typeof SWIG_ERROR__STATE_ERROR; + | typeof SWIG_ERROR__INVALID_SIGNATURE + | typeof SWIG_ERROR__INVALID_INSTRUCTION_DATA_TOO_SHORT + | typeof SWIG_ERROR__OWNER_MISMATCH_SUB_ACCOUNT + | typeof SWIG_ERROR__SUB_ACCOUNT_ALREADY_EXISTS + | typeof SWIG_ERROR__AUTHORITY_CANNOT_CREATE_SUB_ACCOUNT + | typeof SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISCRIMINATOR + | typeof SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISABLED + | typeof SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_SWIG_ID_MISMATCH + | typeof SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_ROLE_ID_MISMATCH + | typeof SWIG_ERROR__INVALID_SWIG_TOKEN_ACCOUNT_OWNER + | typeof SWIG_ERROR__INVALID_PROGRAM_SCOPE_BALANCE_FIELDS + | typeof SWIG_ERROR__ACCOUNT_DATA_MODIFIED_UNEXPECTEDLY + | typeof SWIG_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY + | typeof SWIG_ERROR__SIGN_V1_CANNOT_BE_USED_WITH_SWIG_V2 + | typeof SWIG_ERROR__SIGN_V2_CANNOT_BE_USED_WITH_SWIG_V1 + | typeof SWIG_ERROR__INVALID_SUB_ACCOUNT_INDEX + | typeof SWIG_ERROR__SUB_ACCOUNT_INDEX_MISMATCH + | typeof SWIG_ERROR__SUB_ACCOUNT_ACTION_NOT_FOUND + // SwigStateError (1000-1007) + | typeof SWIG_STATE_ERROR__INVALID_ACCOUNT_DATA + | typeof SWIG_STATE_ERROR__INVALID_ACTION_DATA + | typeof SWIG_STATE_ERROR__INVALID_AUTHORITY_DATA + | typeof SWIG_STATE_ERROR__INVALID_ROLE_DATA + | typeof SWIG_STATE_ERROR__INVALID_SWIG_DATA + | typeof SWIG_STATE_ERROR__ROLE_NOT_FOUND + | typeof SWIG_STATE_ERROR__PERMISSION_LOAD_ERROR + | typeof SWIG_STATE_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION + // SwigAuthenticateError (3000-3032) + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_PAYLOAD + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_DATA_PAYLOAD + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_ED25519_MISSING_AUTHORITY_ACCOUNT + | typeof SWIG_AUTHENTICATE_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION_BASED_AUTH + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_MISSING_PERMISSION + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_PERMISSION_FAILURE + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_DELEGATE_PRESENT + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_NOT_INITIALIZED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TO_MANAGE_AUTHORITY + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_INSUFFICIENT_BALANCE + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_REMOVE_ROOT_AUTHORITY + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SESSION_EXPIRED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE_AGE + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_SIGNATURE_REUSED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_HASH + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_SIGNATURE_REUSED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_STAKE_ACCOUNT_INVALID_STATE + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_KEY_CANNOT_REUSE_SESSION_KEY + | typeof SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_DURATION + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_AUTHORITY_NOT_SWIG + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_INSTRUCTION + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_PUBKEY + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE_HASH + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_AUTHENTICATION_KIND + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_LIMIT_EXCEEDED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_RECURRING_LIMIT_EXCEEDED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_DESTINATION_LIMIT_EXCEEDED + | typeof SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_RECURRING_TOKEN_DESTINATION_LIMIT_EXCEEDED; let swigErrorMessages: Record | undefined; if (process.env.NODE_ENV !== 'production') { swigErrorMessages = { - [SWIG_ERROR__ACCOUNT_BORROW_FAILED]: `Account {0} borrow failed`, - [SWIG_ERROR__ACCOUNT_NOT_EMPTY]: `Account {0} is not empty`, - [SWIG_ERROR__CPI]: `Call from CPI not allowed`, - [SWIG_ERROR__DUPLICATE_AUTHORITY]: `Invalid Authority`, - [SWIG_ERROR__EXPECTED_SIGNER]: `Account {0} must be a signer`, - [SWIG_ERROR__INSTRUCTION_ERROR]: `Instruction Error: {0}`, - [SWIG_ERROR__INVALID_ACCOUNTS]: `Invalid Accounts {0}`, - [SWIG_ERROR__INVALID_AUTHORITY]: `Invalid Authority`, - [SWIG_ERROR__INVALID_AUTHORITY_PAYLOAD]: `Invalid Authority Payload`, - [SWIG_ERROR__INVALID_AUTHORITY_TYPE]: `Invalid Authority Type`, - [SWIG_ERROR__INVALID_OPERATION]: `Invalid Operation {0}`, - [SWIG_ERROR__INVALID_SEED]: `Account {0} Invalid Seeds`, - [SWIG_ERROR__INVALID_SYSTEM_PROGRAM]: `Invalid System Program`, - [SWIG_ERROR__MISSING_INSTRUCTIONS]: `Missing Instructions`, - [SWIG_ERROR__NOT_ON_CURVE]: `Account {0} must be on curve`, - [SWIG_ERROR__OWNER_MISMATCH]: `Account {0} Owner mismatch`, - [SWIG_ERROR__PERMISSION_DENIED]: `Permission Denied {0}`, - [SWIG_ERROR__SERIALIZATION_ERROR]: `Serialization Error`, - [SWIG_ERROR__STATE_ERROR]: `State Error: {0}`, + // SwigError (0-49) + [SWIG_ERROR__INVALID_SWIG_ACCOUNT_DISCRIMINATOR]: `Invalid discriminator in Swig account data`, + [SWIG_ERROR__OWNER_MISMATCH_SWIG_ACCOUNT]: `Swig account owner does not match expected value`, + [SWIG_ERROR__ACCOUNT_NOT_EMPTY_SWIG_ACCOUNT]: `Swig account is not empty when it should be`, + [SWIG_ERROR__NOT_ON_CURVE_SWIG_ACCOUNT]: `Public key in Swig account is not on the curve`, + [SWIG_ERROR__EXPECTED_SIGNER_SWIG_ACCOUNT]: `Expected Swig account to be a signer but it isn't`, + [SWIG_ERROR__STATE_ERROR]: `General state error in program execution`, + [SWIG_ERROR__ACCOUNT_BORROW_FAILED]: `Failed to borrow account data`, + [SWIG_ERROR__INVALID_AUTHORITY_TYPE]: `Invalid authority type specified`, + [SWIG_ERROR__CPI]: `Error during cross-program invocation`, + [SWIG_ERROR__INVALID_SEED_SWIG_ACCOUNT]: `Invalid seed used for Swig account derivation`, + [SWIG_ERROR__MISSING_INSTRUCTIONS]: `Required instructions are missing`, + [SWIG_ERROR__INVALID_AUTHORITY_PAYLOAD]: `Invalid authority payload format`, + [SWIG_ERROR__INVALID_AUTHORITY_NOT_FOUND_BY_ROLE_ID]: `Authority not found for given role ID`, + [SWIG_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION]: `Authority must have at least one action`, + [SWIG_ERROR__INSTRUCTION_EXECUTION_ERROR]: `Error during instruction execution`, + [SWIG_ERROR__SERIALIZATION_ERROR]: `Error during data serialization`, + [SWIG_ERROR__INVALID_SWIG_SIGN_INSTRUCTION_DATA_TOO_SHORT]: `Sign instruction data is too short`, + [SWIG_ERROR__INVALID_SWIG_REMOVE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT]: `Remove authority instruction data is too short`, + [SWIG_ERROR__INVALID_SWIG_ADD_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT]: `Add authority instruction data is too short`, + [SWIG_ERROR__INVALID_SWIG_UPDATE_AUTHORITY_INSTRUCTION_DATA_TOO_SHORT]: `Update authority instruction data is too short`, + [SWIG_ERROR__INVALID_SWIG_CREATE_INSTRUCTION_DATA_TOO_SHORT]: `Create instruction data is too short`, + [SWIG_ERROR__INVALID_SWIG_CREATE_SESSION_INSTRUCTION_DATA_TOO_SHORT]: `Create session instruction data is too short`, + [SWIG_ERROR__INVALID_ACCOUNTS_LENGTH]: `Invalid number of accounts provided`, + [SWIG_ERROR__INVALID_ACCOUNTS_SWIG_MUST_BE_FIRST]: `Swig account must be the first account in the list`, + [SWIG_ERROR__INVALID_SYSTEM_PROGRAM]: `Invalid system program account`, + [SWIG_ERROR__DUPLICATE_AUTHORITY]: `Authority already exists`, + [SWIG_ERROR__INVALID_OPERATION]: `Invalid operation attempted`, + [SWIG_ERROR__INVALID_ALIGNMENT]: `Data alignment error`, + [SWIG_ERROR__INVALID_SEED_SUB_ACCOUNT]: `Invalid seed used for sub-account derivation`, + [SWIG_ERROR__INSUFFICIENT_FUNDS]: `Insufficient funds for operation`, + [SWIG_ERROR__OWNER_MISMATCH_TOKEN_ACCOUNT]: `Token account owner mismatch`, + [SWIG_ERROR__PERMISSION_DENIED]: `Permission denied for operation`, + [SWIG_ERROR__INVALID_SIGNATURE]: `Invalid signature provided`, + [SWIG_ERROR__INVALID_INSTRUCTION_DATA_TOO_SHORT]: `Instruction data is too short`, + [SWIG_ERROR__OWNER_MISMATCH_SUB_ACCOUNT]: `Sub-account owner mismatch`, + [SWIG_ERROR__SUB_ACCOUNT_ALREADY_EXISTS]: `Sub-account already exists`, + [SWIG_ERROR__AUTHORITY_CANNOT_CREATE_SUB_ACCOUNT]: `Authority cannot create sub-account`, + [SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISCRIMINATOR]: `Invalid discriminator in sub-account data`, + [SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_DISABLED]: `Sub-account is disabled`, + [SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_SWIG_ID_MISMATCH]: `Sub-account Swig ID mismatch`, + [SWIG_ERROR__INVALID_SWIG_SUB_ACCOUNT_ROLE_ID_MISMATCH]: `Sub-account role ID mismatch`, + [SWIG_ERROR__INVALID_SWIG_TOKEN_ACCOUNT_OWNER]: `Invalid token account owner`, + [SWIG_ERROR__INVALID_PROGRAM_SCOPE_BALANCE_FIELDS]: `Invalid program scope balance field configuration`, + [SWIG_ERROR__ACCOUNT_DATA_MODIFIED_UNEXPECTEDLY]: `Account data was modified in unexpected ways during instruction execution`, + [SWIG_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY]: `Cannot update root authority (ID 0)`, + [SWIG_ERROR__SIGN_V1_CANNOT_BE_USED_WITH_SWIG_V2]: `SignV1 instruction cannot be used with Swig v2 accounts`, + [SWIG_ERROR__SIGN_V2_CANNOT_BE_USED_WITH_SWIG_V1]: `SignV2 instruction cannot be used with Swig v1 accounts`, + [SWIG_ERROR__INVALID_SUB_ACCOUNT_INDEX]: `Invalid sub-account index (must be 0-254)`, + [SWIG_ERROR__SUB_ACCOUNT_INDEX_MISMATCH]: `Sub-account index mismatch between action and instruction`, + [SWIG_ERROR__SUB_ACCOUNT_ACTION_NOT_FOUND]: `Sub-account action not found for the specified index. Note: Sub-account indices do NOT need to be sequential - they can be created in any order (e.g., 0, 5, 2, 10)`, + // SwigStateError (1000-1007) + [SWIG_STATE_ERROR__INVALID_ACCOUNT_DATA]: `Account data is invalid or corrupted`, + [SWIG_STATE_ERROR__INVALID_ACTION_DATA]: `Action data is invalid or malformed`, + [SWIG_STATE_ERROR__INVALID_AUTHORITY_DATA]: `Authority data is invalid or malformed`, + [SWIG_STATE_ERROR__INVALID_ROLE_DATA]: `Role data is invalid or malformed`, + [SWIG_STATE_ERROR__INVALID_SWIG_DATA]: `Swig account data is invalid or malformed`, + [SWIG_STATE_ERROR__ROLE_NOT_FOUND]: `Specified role could not be found`, + [SWIG_STATE_ERROR__PERMISSION_LOAD_ERROR]: `Error loading permissions`, + [SWIG_STATE_ERROR__INVALID_AUTHORITY_MUST_HAVE_AT_LEAST_ONE_ACTION]: `Adding an authority requires at least one action`, + // SwigAuthenticateError (3000-3032) + [SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY]: `Invalid authority provided`, + [SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_PAYLOAD]: `Invalid authority payload format`, + [SWIG_AUTHENTICATE_ERROR__INVALID_DATA_PAYLOAD]: `Invalid data payload format`, + [SWIG_AUTHENTICATE_ERROR__INVALID_AUTHORITY_ED25519_MISSING_AUTHORITY_ACCOUNT]: `Missing Ed25519 authority account`, + [SWIG_AUTHENTICATE_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION_BASED_AUTH]: `Authority does not support session-based authentication`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED]: `Generic permission denied error`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_MISSING_PERMISSION]: `Missing required permission`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_PERMISSION_FAILURE]: `Token account permission check failed`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_DELEGATE_PRESENT]: `Token account has an active delegate or close authority`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_NOT_INITIALIZED]: `Token account is not initialized`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TO_MANAGE_AUTHORITY]: `No permission to manage authority`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_INSUFFICIENT_BALANCE]: `Insufficient balance for operation`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_REMOVE_ROOT_AUTHORITY]: `Cannot remove root authority`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_CANNOT_UPDATE_ROOT_AUTHORITY]: `Cannot update root authority`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SESSION_EXPIRED]: `Session has expired`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE]: `Invalid Secp256k1 signature`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_SIGNATURE_AGE]: `Secp256k1 signature age is invalid`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_SIGNATURE_REUSED]: `Secp256k1 signature has been reused`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256K1_INVALID_HASH]: `Invalid Secp256k1 hash`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_SIGNATURE_REUSED]: `Secp256r1 signature has been reused`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_STAKE_ACCOUNT_INVALID_STATE]: `Stake account is in an invalid state`, + [SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_KEY_CANNOT_REUSE_SESSION_KEY]: `Cannot reuse session key`, + [SWIG_AUTHENTICATE_ERROR__INVALID_SESSION_DURATION]: `Invalid session duration`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_ACCOUNT_AUTHORITY_NOT_SWIG]: `Token account authority is not the Swig account`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_INSTRUCTION]: `Invalid Secp256r1 instruction`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_PUBKEY]: `Invalid Secp256r1 public key`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE_HASH]: `Invalid Secp256r1 message hash`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_MESSAGE]: `Invalid Secp256r1 message`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SECP256R1_INVALID_AUTHENTICATION_KIND]: `Invalid Secp256r1 authentication kind`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_LIMIT_EXCEEDED]: `SOL destination limit exceeded`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_SOL_DESTINATION_RECURRING_LIMIT_EXCEEDED]: `SOL destination recurring limit exceeded`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_TOKEN_DESTINATION_LIMIT_EXCEEDED]: `Token destination limit exceeded`, + [SWIG_AUTHENTICATE_ERROR__PERMISSION_DENIED_RECURRING_TOKEN_DESTINATION_LIMIT_EXCEEDED]: `Token destination recurring limit exceeded`, }; } +/** + * Get the error message for a Swig error code. + * Supports all error ranges: SwigError (0-49), SwigStateError (1000-1007), and SwigAuthenticateError (3000-3032). + * + * @param code - The error code to get the message for + * @returns The error message string + * + * @example + * ```ts + * const message = getSwigErrorMessage(47); // "Invalid sub-account index (must be 0-254)" + * const stateMsg = getSwigErrorMessage(1000); // "Account data is invalid or corrupted" + * const authMsg = getSwigErrorMessage(3005); // "Generic permission denied error" + * ``` + */ export function getSwigErrorMessage(code: SwigError): string { if (process.env.NODE_ENV !== 'production') { return (swigErrorMessages as Record)[code]; diff --git a/packages/coder/src/instructions/subAccountCreateV1.ts b/packages/coder/src/instructions/subAccountCreateV1.ts index 8157ba38..85abe4ec 100644 --- a/packages/coder/src/instructions/subAccountCreateV1.ts +++ b/packages/coder/src/instructions/subAccountCreateV1.ts @@ -27,6 +27,7 @@ export type SubAccountCreateV1InstructionData = { _padding1: ReadonlyUint8Array; roleId: number; bump: number; + subAccountIndex: number; _padding2: ReadonlyUint8Array; authorityPayload: ReadonlyUint8Array; }; @@ -34,6 +35,7 @@ export type SubAccountCreateV1InstructionData = { export type SubAccountCreateV1InstructionDataArgs = { roleId: number; bump: number; + subAccountIndex: number; authorityPayload: ReadonlyUint8Array; }; @@ -45,14 +47,15 @@ export function getSubAccountCreateV1InstructionDataCodec() { ['_padding1', fixEncoderSize(getBytesEncoder(), 2)], ['roleId', getU32Encoder()], ['bump', getU8Encoder()], - ['_padding2', fixEncoderSize(getBytesEncoder(), 7)], + ['subAccountIndex', getU8Encoder()], + ['_padding2', fixEncoderSize(getBytesEncoder(), 6)], ['authorityPayload', getBytesEncoder()], ]), (value) => ({ ...value, discriminator: Discriminator.SubAccountCreateV1, _padding1: new Uint8Array(2), - _padding2: new Uint8Array(7), + _padding2: new Uint8Array(6), }), ); @@ -64,13 +67,14 @@ export function getSubAccountCreateV1InstructionDataCodec() { ['_padding1', fixEncoderSize(getBytesEncoder(), 2)], ['roleId', getU32Encoder()], ['bump', getU8Encoder()], - ['_padding2', fixEncoderSize(getBytesEncoder(), 7)], + ['subAccountIndex', getU8Encoder()], + ['_padding2', fixEncoderSize(getBytesEncoder(), 6)], ]), (value) => ({ ...value, discriminator: Discriminator.SubAccountCreateV1, _padding1: new Uint8Array(2), - _padding2: new Uint8Array(7), + _padding2: new Uint8Array(6), }), ); @@ -79,7 +83,8 @@ export function getSubAccountCreateV1InstructionDataCodec() { ['_padding1', fixDecoderSize(getBytesDecoder(), 2)], ['roleId', getU32Decoder()], ['bump', getU8Decoder()], - ['_padding2', fixDecoderSize(getBytesDecoder(), 7)], + ['subAccountIndex', getU8Decoder()], + ['_padding2', fixDecoderSize(getBytesDecoder(), 6)], ['authorityPayload', getBytesDecoder()], ]); diff --git a/packages/coder/src/types/subAccount.ts b/packages/coder/src/types/subAccount.ts index 46fd45c6..d95bbc28 100644 --- a/packages/coder/src/types/subAccount.ts +++ b/packages/coder/src/types/subAccount.ts @@ -23,7 +23,8 @@ export type SubAccount = { subAccount: ReadonlyUint8Array; bump: number; enabled: boolean; - _padding: ReadonlyUint8Array; + subAccountIndex: number; + _padding: number; roleId: number; swigId: ReadonlyUint8Array; }; @@ -32,7 +33,8 @@ export type SubAccountData = { subAccount: ReadonlyUint8Array; bump: number; enabled: boolean; - _padding: ReadonlyUint8Array; + subAccountIndex: number; + _padding: number; roleId: number; swigId: ReadonlyUint8Array; }; @@ -43,13 +45,14 @@ export function getSubAccountEncoder(): Encoder { ['subAccount', fixEncoderSize(getBytesEncoder(), 32)], ['bump', getU8Encoder()], ['enabled', getBooleanEncoder()], - ['_padding', fixEncoderSize(getBytesEncoder(), 2)], + ['subAccountIndex', getU8Encoder()], + ['_padding', getU8Encoder()], ['roleId', getU32Encoder()], ['swigId', fixEncoderSize(getBytesEncoder(), 32)], ]), (value) => ({ ...value, - _padding: new Uint8Array(2), + _padding: 0, }), ); } @@ -59,7 +62,8 @@ export function getSubAccountDecoder(): Decoder { ['subAccount', fixDecoderSize(getBytesDecoder(), 32)], ['bump', getU8Decoder()], ['enabled', getBooleanDecoder()], - ['_padding', fixDecoderSize(getBytesDecoder(), 2)], + ['subAccountIndex', getU8Decoder()], + ['_padding', getU8Decoder()], ['roleId', getU32Decoder()], ['swigId', fixDecoderSize(getBytesDecoder(), 32)], ]); diff --git a/packages/kit/src/accounts/swig.ts b/packages/kit/src/accounts/swig.ts index 27dd202b..2dbad4d2 100644 --- a/packages/kit/src/accounts/swig.ts +++ b/packages/kit/src/accounts/swig.ts @@ -169,6 +169,57 @@ export async function findSwigSubAccountPda( )[0]; } +/** + * Utility for deriving a Swig SubAccount PDA with index + * Index 0 uses legacy 3-seed derivation for backwards compatibility + * Indices 1-254 use new 4-seed derivation with index + * @param swigId Swig ID + * @param roleId Role ID + * @param subAccountIndex Sub-account index (0-254, defaults to 0) + * @returns Promise
+ */ +export async function findSwigSubAccountPdaWithIndex( + swigId: Uint8Array, + roleId: number, + subAccountIndex: number = 0, +) { + if (subAccountIndex < 0 || subAccountIndex > 254) { + throw new Error('Sub-account index must be between 0 and 254'); + } + + const roleIdU32 = new Uint8Array(4); + const view = new DataView(roleIdU32.buffer); + view.setUint32(0, roleId, true); + + if (subAccountIndex === 0) { + // Legacy derivation for index 0 (backwards compatible) + return ( + await getProgramDerivedAddress({ + programAddress: SWIG_PROGRAM_ADDRESS, + seeds: [ + getUtf8Encoder().encode('sub-account'), + getBytesEncoder().encode(swigId), + getBytesEncoder().encode(roleIdU32), + ], + }) + )[0]; + } else { + // New derivation for index 1-254 with index in seeds + const indexU8 = new Uint8Array([subAccountIndex]); + return ( + await getProgramDerivedAddress({ + programAddress: SWIG_PROGRAM_ADDRESS, + seeds: [ + getUtf8Encoder().encode('sub-account'), + getBytesEncoder().encode(swigId), + getBytesEncoder().encode(roleIdU32), + getBytesEncoder().encode(indexU8), + ], + }) + )[0]; + } +} + export function getSwigAccountAddress(swig: Swig): Address { return getSwigAccountAddressRaw(swig).toAddress(); } diff --git a/packages/lib/src/actions/builder.ts b/packages/lib/src/actions/builder.ts index b4571e47..ca30d51d 100644 --- a/packages/lib/src/actions/builder.ts +++ b/packages/lib/src/actions/builder.ts @@ -228,13 +228,16 @@ export class ActionsBuilder { } /** + * Grants permission for an authority to access the wallet, useful when it * controls a subaccount + * @param subAccountIndex - Optional sub-account index (0-254, defaults to 0) */ - subAccount(): this { + subAccount(subAccountIndex: number = 0): this { this._actionConfigs.push( new SubAccountConfig({ subAccount: new Uint8Array(32), - _padding: new Uint8Array(2), + subAccountIndex, + _padding: 0, bump: 0, enabled: false, roleId: 0, @@ -282,7 +285,7 @@ export class ActionsBuilder { /** * Enables a Spend-once SOL Spend - * @param payload.amount ID of the program to enable + * @param payload.amount amount allowed to spend */ solLimit(payload: { amount: bigint }): this { this._actionConfigs.push(new SolLimitConfig(payload)); diff --git a/packages/lib/src/authority/abstract.ts b/packages/lib/src/authority/abstract.ts index ed09ed86..9c77fe4c 100644 --- a/packages/lib/src/authority/abstract.ts +++ b/packages/lib/src/authority/abstract.ts @@ -126,6 +126,7 @@ export abstract class Authority { swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; options?: InstructionDataOptions; }): Promise; diff --git a/packages/lib/src/authority/ed25519/session.ts b/packages/lib/src/authority/ed25519/session.ts index 66313904..189fd7fc 100644 --- a/packages/lib/src/authority/ed25519/session.ts +++ b/packages/lib/src/authority/ed25519/session.ts @@ -9,7 +9,7 @@ import { SolPublicKey, type SolPublicKeyData, } from '../../solana'; -import { findSwigSubAccountPdaRaw } from '../../utils'; +import { findSwigSubAccountPdaRawWithIndex } from '../../utils'; import { Authority, SessionBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; import { Ed25519Instruction } from '../instructions'; @@ -176,10 +176,13 @@ export class Ed25519SessionAuthority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Ed25519Instruction.subAccountCreateV1Instruction( { @@ -191,6 +194,7 @@ export class Ed25519SessionAuthority roleId: args.roleId, authorityData: this.data, bump, + subAccountIndex, }, ); } diff --git a/packages/lib/src/authority/ed25519/token.ts b/packages/lib/src/authority/ed25519/token.ts index b523c649..e5f4c70e 100644 --- a/packages/lib/src/authority/ed25519/token.ts +++ b/packages/lib/src/authority/ed25519/token.ts @@ -9,7 +9,7 @@ import { SolPublicKey, type SolPublicKeyData, } from '../../solana'; -import { findSwigSubAccountPdaRaw } from '../../utils'; +import { findSwigSubAccountPdaRawWithIndex } from '../../utils'; import { Authority, TokenBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; import { Ed25519Instruction } from '../instructions'; @@ -123,10 +123,13 @@ export class Ed25519Authority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Ed25519Instruction.subAccountCreateV1Instruction( { @@ -138,6 +141,7 @@ export class Ed25519Authority roleId: args.roleId, authorityData: this.data, bump, + subAccountIndex, }, ); } diff --git a/packages/lib/src/authority/secp256k1/session.ts b/packages/lib/src/authority/secp256k1/session.ts index 1191b618..2a93e515 100644 --- a/packages/lib/src/authority/secp256k1/session.ts +++ b/packages/lib/src/authority/secp256k1/session.ts @@ -16,7 +16,7 @@ import { } from '../../solana'; import { compressedPubkeyToAddress, - findSwigSubAccountPdaRaw, + findSwigSubAccountPdaRawWithIndex, } from '../../utils'; import { SessionBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; @@ -206,11 +206,14 @@ export class Secp256k1SessionAuthority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; options: InstructionDataOptions; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Secp256k1Instruction.subAccountCreateV1Instruction( { @@ -222,6 +225,7 @@ export class Secp256k1SessionAuthority roleId: args.roleId, authorityData: this.data, bump, + subAccountIndex, }, { ...args.options, odometer: args.options.odometer ?? this.odometer() }, ); diff --git a/packages/lib/src/authority/secp256k1/token.ts b/packages/lib/src/authority/secp256k1/token.ts index 3b7bfa13..b5829db9 100644 --- a/packages/lib/src/authority/secp256k1/token.ts +++ b/packages/lib/src/authority/secp256k1/token.ts @@ -12,7 +12,7 @@ import { } from '../../solana'; import { compressedPubkeyToAddress, - findSwigSubAccountPdaRaw, + findSwigSubAccountPdaRawWithIndex, } from '../../utils'; import { TokenBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; @@ -165,11 +165,14 @@ export class Secp256k1Authority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; options: InstructionDataOptions; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Secp256k1Instruction.subAccountCreateV1Instruction( { @@ -181,6 +184,7 @@ export class Secp256k1Authority roleId: args.roleId, authorityData: this.publicKeyBytes, bump, + subAccountIndex, }, { ...args.options, odometer: args.options.odometer ?? this.odometer() }, ); diff --git a/packages/lib/src/authority/secp256r1/session.ts b/packages/lib/src/authority/secp256r1/session.ts index fcba6cce..7cf55054 100644 --- a/packages/lib/src/authority/secp256r1/session.ts +++ b/packages/lib/src/authority/secp256r1/session.ts @@ -13,7 +13,7 @@ import { SolPublicKey, type SolPublicKeyData, } from '../../solana'; -import { findSwigSubAccountPdaRaw } from '../../utils'; +import { findSwigSubAccountPdaRawWithIndex } from '../../utils'; import { SessionBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; import { Ed25519Instruction, Secp256r1Instruction } from '../instructions'; @@ -186,11 +186,14 @@ export class Secp256r1SessionAuthority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; options: InstructionDataOptions; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Secp256r1Instruction.subAccountCreateV1Instruction( { @@ -202,6 +205,7 @@ export class Secp256r1SessionAuthority roleId: args.roleId, authorityData: this.data, bump, + subAccountIndex, }, { ...args.options, odometer: this.odometer() ?? args.options.odometer }, ); diff --git a/packages/lib/src/authority/secp256r1/token.ts b/packages/lib/src/authority/secp256r1/token.ts index 8db1a7b0..babc3f2e 100644 --- a/packages/lib/src/authority/secp256r1/token.ts +++ b/packages/lib/src/authority/secp256r1/token.ts @@ -10,7 +10,7 @@ import { SolPublicKey, type SolPublicKeyData, } from '../../solana'; -import { findSwigSubAccountPdaRaw } from '../../utils'; +import { findSwigSubAccountPdaRawWithIndex } from '../../utils'; import { TokenBasedAuthority } from '../abstract'; import type { CreateAuthorityInfo } from '../createAuthority'; import { Secp256r1Instruction } from '../instructions'; @@ -154,11 +154,14 @@ export class Secp256r1Authority swigAddress: SolPublicKeyData; swigId: Uint8Array; roleId: number; + subAccountIndex?: number; options: InstructionDataOptions; }) { - const [subAccount, bump] = await findSwigSubAccountPdaRaw( + const subAccountIndex = args.subAccountIndex ?? 0; + const [subAccount, bump] = await findSwigSubAccountPdaRawWithIndex( args.swigId, args.roleId, + subAccountIndex, ); return Secp256r1Instruction.subAccountCreateV1Instruction( { @@ -170,6 +173,7 @@ export class Secp256r1Authority roleId: args.roleId, authorityData: this.publicKeyBytes, bump, + subAccountIndex, }, { ...args.options, odometer: args.options.odometer ?? this.odometer() }, ); diff --git a/packages/lib/src/swig/index.ts b/packages/lib/src/swig/index.ts index be86bfa3..e6abe5ff 100644 --- a/packages/lib/src/swig/index.ts +++ b/packages/lib/src/swig/index.ts @@ -20,6 +20,7 @@ import { } from '../solana'; import { findSwigSubAccountPdaRaw, + findSwigSubAccountPdaRawWithIndex, findSwigSystemAddressPdaRaw, getUnprefixedSecpBytes, } from '../utils'; @@ -305,8 +306,13 @@ export const getSignInstructionContext = async ( ); if (withSubAccount) { + const subAccountIndex = options?.subAccountIndex ?? 0; const subAccount = ( - await findSwigSubAccountPdaRaw(role.swigId, role.id) + await findSwigSubAccountPdaRawWithIndex( + role.swigId, + role.id, + subAccountIndex, + ) )[0]; return role.authority.subAccountSign({ @@ -385,6 +391,7 @@ export const getCreateSubAccountInstructionContext = async ( swigId: role.swigId, payer, roleId: role.id, + subAccountIndex: options?.subAccountIndex, options, }); }; @@ -583,6 +590,7 @@ export type SwigOptions = { signingFn?: SigningFn; currentSlot?: bigint; payer?: SolPublicKeyData; + subAccountIndex?: number; }; export type WithdrawSubAccountArgs< diff --git a/packages/lib/src/utils.ts b/packages/lib/src/utils.ts index e80a9476..477a5922 100644 --- a/packages/lib/src/utils.ts +++ b/packages/lib/src/utils.ts @@ -61,6 +61,7 @@ export async function findSwigSystemAddressPdaRaw( /** * Utility for deriving a Swig SubAccount PDA (async) + * Uses legacy 3-seed derivation for backwards compatibility (index 0) * @param swigId Swig ID * @param roleId number * @returns Promise<[Address, number]> (address, bump) @@ -81,8 +82,56 @@ async function findSwigSubAccountPda(swigId: Uint8Array, roleId: number) { }); } +/** + * Utility for deriving a Swig SubAccount PDA with index (async) + * Index 0 uses legacy 3-seed derivation for backwards compatibility + * Indices 1-254 use new 4-seed derivation with index + * @param swigId Swig ID + * @param roleId number + * @param subAccountIndex Sub-account index (0-254) + * @returns Promise<[Address, number]> (address, bump) + */ +async function findSwigSubAccountPdaWithIndex( + swigId: Uint8Array, + roleId: number, + subAccountIndex: number, +) { + if (subAccountIndex < 0 || subAccountIndex > 254) { + throw new Error('Sub-account index must be between 0 and 254'); + } + + const roleIdU32 = new Uint8Array(4); + const view = new DataView(roleIdU32.buffer); + view.setUint32(0, roleId, true); + + if (subAccountIndex === 0) { + // Legacy derivation for index 0 (backwards compatible) + return await getProgramDerivedAddress({ + programAddress: address(SWIG_PROGRAM_ADDRESS_STRING), + seeds: [ + getUtf8Encoder().encode('sub-account'), + getBytesEncoder().encode(swigId), + getBytesEncoder().encode(roleIdU32), + ], + }); + } else { + // New derivation for index 1-254 with index in seeds + const indexU8 = new Uint8Array([subAccountIndex]); + return await getProgramDerivedAddress({ + programAddress: address(SWIG_PROGRAM_ADDRESS_STRING), + seeds: [ + getUtf8Encoder().encode('sub-account'), + getBytesEncoder().encode(swigId), + getBytesEncoder().encode(roleIdU32), + getBytesEncoder().encode(indexU8), + ], + }); + } +} + /** * Utility for deriving a Swig SubAccount PDA (async) + * Uses legacy derivation (index 0) for backwards compatibility * @param swigId Swig ID * @param roleId number * @returns Promise<[Address, number]> (address, bump) @@ -95,6 +144,28 @@ export async function findSwigSubAccountPdaRaw( return [new SolPublicKey(address), bump]; } +/** + * Utility for deriving a Swig SubAccount PDA with index (async) + * Index 0 uses legacy 3-seed derivation for backwards compatibility + * Indices 1-254 use new 4-seed derivation with index + * @param swigId Swig ID + * @param roleId number + * @param subAccountIndex Sub-account index (0-254, defaults to 0) + * @returns Promise<[Address, number]> (address, bump) + */ +export async function findSwigSubAccountPdaRawWithIndex( + swigId: Uint8Array, + roleId: number, + subAccountIndex: number = 0, +): Promise<[SolPublicKey, number]> { + const [address, bump] = await findSwigSubAccountPdaWithIndex( + swigId, + roleId, + subAccountIndex, + ); + return [new SolPublicKey(address), bump]; +} + export function compressedPubkeyToAddress( compressed: Uint8Array | string, ): Uint8Array { diff --git a/packages/lib/tests/authorityInstructions.test.ts b/packages/lib/tests/authorityInstructions.test.ts index 888700b7..befbf1ac 100644 --- a/packages/lib/tests/authorityInstructions.test.ts +++ b/packages/lib/tests/authorityInstructions.test.ts @@ -99,12 +99,13 @@ describe('Ed25519Instruction', () => { const data = { authorityData, subAccountId: dummyUint8(), - authorityType: 0, + authorityType: 1, actions: dummyUint8(), noOfActions: 1, subAccountData: dummyUint8(), roleId: 1, bump: 1, + subAccountIndex: 0, }; const ix = await Ed25519Instruction.subAccountCreateV1Instruction( accounts, @@ -280,6 +281,7 @@ describe('Secp256k1Instruction', () => { authorityData: dummyUint8(32), roleId: 1, bump: 1, + subAccountIndex: 0, }; const ix = await Secp256k1Instruction.subAccountCreateV1Instruction( accounts, diff --git a/packages/lib/tests/instructions.test.ts b/packages/lib/tests/instructions.test.ts index 7c4cdfb5..98eb7448 100644 --- a/packages/lib/tests/instructions.test.ts +++ b/packages/lib/tests/instructions.test.ts @@ -142,9 +142,10 @@ describe('SwigInstructionV1', () => { SolAccountMeta.fromKitAccountMeta, ) as SubAccountCreateV1BaseAccountMetas; const data = { - roleId: 0, + roleId: 1, bump: 255, authorityPayload: new Uint8Array([]), + subAccountIndex: 0, }; const ix = SwigInstructionV1.subAccountCreate(accounts, data); expect(ix).toBeInstanceOf(SwigInstructionContext);