diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2f9fdbf6..7f3a189da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ com/project-serum/anchor/pull/1841)). * lang: Add `PartialEq` and `Eq` for `anchor_lang::Error` ([#1544](https://github.com/project-serum/anchor/pull/1544)). * cli: Add `b` and `t` aliases for `build` and `test` respectively ([#1823](https://github.com/project-serum/anchor/pull/1823)). * spl: Add `sync_native` token program CPI wrapper function ([#1833](https://github.com/project-serum/anchor/pull/1833)). +* ts: Implement a coder for system program ([#1920](https://github.com/project-serum/anchor/pull/1920)). ### Fixes diff --git a/tests/custom-coder/Anchor.toml b/tests/custom-coder/Anchor.toml index 6ea244cafb..ec4e7c95bd 100644 --- a/tests/custom-coder/Anchor.toml +++ b/tests/custom-coder/Anchor.toml @@ -1,6 +1,7 @@ [programs.localnet] custom_coder = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" spl_token = "FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w" +native_system = "9NxAd91hhJ3ZBTHytYP894y4ESRKG7n8VbLgdyYGJFLB" [registry] url = "https://anchor.projectserum.com" diff --git a/tests/custom-coder/package.json b/tests/custom-coder/package.json index 802bc74d8c..f14edc3a1e 100644 --- a/tests/custom-coder/package.json +++ b/tests/custom-coder/package.json @@ -15,5 +15,9 @@ }, "scripts": { "test": "anchor test" + }, + "dependencies": { + "mocha": "^10.0.0", + "ts-mocha": "^10.0.0" } } diff --git a/tests/custom-coder/programs/native-system/Cargo.toml b/tests/custom-coder/programs/native-system/Cargo.toml new file mode 100644 index 0000000000..b805cdb25f --- /dev/null +++ b/tests/custom-coder/programs/native-system/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "native-system" +version = "0.1.0" +description = "Created with Anchor" +rust-version = "1.56" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "native_system" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/custom-coder/programs/native-system/Xargo.toml b/tests/custom-coder/programs/native-system/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/custom-coder/programs/native-system/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/custom-coder/programs/native-system/src/lib.rs b/tests/custom-coder/programs/native-system/src/lib.rs new file mode 100644 index 0000000000..8ac2b71574 --- /dev/null +++ b/tests/custom-coder/programs/native-system/src/lib.rs @@ -0,0 +1,214 @@ +use anchor_lang::prelude::*; + +declare_id!("9NxAd91hhJ3ZBTHytYP894y4ESRKG7n8VbLgdyYGJFLB"); + +#[program] +pub mod native_system { + use super::*; + + pub fn create_account( + ctx: Context, + lamports: u64, + space: u64, + owner: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn assign(ctx: Context, owner: Pubkey) -> Result<()> { + Ok(()) + } + + pub fn transfer(ctx: Context, lamports: u64) -> Result<()> { + Ok(()) + } + + pub fn create_account_with_seed( + ctx: Context, + base: Pubkey, + seed: String, + lamports: u64, + space: u64, + owner: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn advance_nonce_account( + ctx: Context, + authorized: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn withdraw_nonce_account(ctx: Context, lamports: u64) -> Result<()> { + Ok(()) + } + + pub fn initialize_nonce_account( + ctx: Context, + authorized: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn authorize_nonce_account( + ctx: Context, + authorized: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn allocate(ctx: Context, space: u64) -> Result<()> { + Ok(()) + } + + pub fn allocate_with_seed( + ctx: Context, + base: Pubkey, + seed: String, + space: u64, + owner: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn assign_with_seed( + ctx: Context, + base: Pubkey, + seed: String, + owner: Pubkey, + ) -> Result<()> { + Ok(()) + } + + pub fn transfer_with_seed( + ctx: Context, + lamports: u64, + seed: String, + owner: Pubkey, + ) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateAccount<'info> { + #[account(mut)] + from: Signer<'info>, + #[account(mut)] + to: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Assign<'info> { + #[account(mut)] + pubkey: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Transfer<'info> { + #[account(mut)] + from: Signer<'info>, + #[account(mut)] + /// CHECK: + to: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct CreateAccountWithSeed<'info> { + #[account(mut)] + from: Signer<'info>, + #[account(mut)] + /// CHECK: + to: AccountInfo<'info>, + base: Signer<'info>, +} + +#[derive(Accounts)] +pub struct AdvanceNonceAccount<'info> { + #[account(mut)] + /// CHECK: + nonce: AccountInfo<'info>, + /// CHECK: + recent_blockhashes: AccountInfo<'info>, + authorized: Signer<'info>, +} + +#[derive(Accounts)] +pub struct WithdrawNonceAccount<'info> { + #[account(mut)] + /// CHECK: + nonce: AccountInfo<'info>, + #[account(mut)] + /// CHECK: + to: AccountInfo<'info>, + /// CHECK: + recent_blockhashes: AccountInfo<'info>, + rent: Sysvar<'info, Rent>, + authorized: Signer<'info>, +} + +#[derive(Accounts)] +pub struct InitializeNonceAccount<'info> { + #[account(mut)] + nonce: Signer<'info>, + /// CHECK: + recent_blockhashes: AccountInfo<'info>, + rent: Sysvar<'info, Rent>, +} + +#[derive(Accounts)] +pub struct AuthorizeNonceAccount<'info> { + #[account(mut)] + /// CHECK: + nonce: AccountInfo<'info>, + authorized: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Allocate<'info> { + #[account(mut)] + pubkey: Signer<'info>, +} + +#[derive(Accounts)] +pub struct AllocateWithSeed<'info> { + #[account(mut)] + /// CHECK: + account: AccountInfo<'info>, + base: Signer<'info>, +} + +#[derive(Accounts)] +pub struct AssignWithSeed<'info> { + #[account(mut)] + /// CHECK: + account: AccountInfo<'info>, + base: Signer<'info>, +} + +#[derive(Accounts)] +pub struct TransferWithSeed<'info> { + #[account(mut)] + /// CHECK: + from: AccountInfo<'info>, + base: Signer<'info>, + #[account(mut)] + /// CHECK: + to: AccountInfo<'info>, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct FeeCalculator { + pub lamports_per_signature: u64, +} + +#[account] +pub struct Nonce { + pub version: u32, + pub state: u32, + pub authorized_pubkey: Pubkey, + pub nonce: Pubkey, + pub fee_calculator: FeeCalculator, +} diff --git a/tests/custom-coder/programs/spl-token/src/lib.rs b/tests/custom-coder/programs/spl-token/src/lib.rs index 15f222e4ee..07583dd4c2 100644 --- a/tests/custom-coder/programs/spl-token/src/lib.rs +++ b/tests/custom-coder/programs/spl-token/src/lib.rs @@ -131,31 +131,41 @@ pub mod spl_token { #[derive(Accounts)] pub struct InitializeMint<'info> { #[account(mut)] + /// CHECK: mint: AccountInfo<'info>, + /// CHECK: rent: AccountInfo<'info>, } #[derive(Accounts)] pub struct InitializeAccount<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, + /// CHECK: authority: AccountInfo<'info>, + /// CHECK: rent: AccountInfo<'info>, } #[derive(Accounts)] pub struct InitializeMultisig<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: rent: AccountInfo<'info>, } #[derive(Accounts)] pub struct Transfer<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, #[account(mut)] + /// CHECK: destination: AccountInfo<'info>, authority: Signer<'info>, } @@ -163,7 +173,9 @@ pub struct Transfer<'info> { #[derive(Accounts)] pub struct Approve<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, + /// CHECK: delegate: AccountInfo<'info>, authority: Signer<'info>, } @@ -171,6 +183,7 @@ pub struct Approve<'info> { #[derive(Accounts)] pub struct Revoke<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, authority: Signer<'info>, } @@ -178,6 +191,7 @@ pub struct Revoke<'info> { #[derive(Accounts)] pub struct SetAuthority<'info> { #[account(mut)] + /// CHECK: pub mint: AccountInfo<'info>, pub authority: Signer<'info>, } @@ -185,8 +199,10 @@ pub struct SetAuthority<'info> { #[derive(Accounts)] pub struct MintTo<'info> { #[account(mut)] + /// CHECK: pub mint: AccountInfo<'info>, #[account(mut)] + /// CHECK: pub to: AccountInfo<'info>, pub authority: Signer<'info>, } @@ -194,8 +210,10 @@ pub struct MintTo<'info> { #[derive(Accounts)] pub struct Burn<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, #[account(mut)] + /// CHECK: mint: AccountInfo<'info>, authority: Signer<'info>, } @@ -203,16 +221,21 @@ pub struct Burn<'info> { #[derive(Accounts)] pub struct CloseAccount<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, #[account(mut)] + /// CHECK: destination: AccountInfo<'info>, + /// CHECK: authority: AccountInfo<'info>, } #[derive(Accounts)] pub struct FreezeAccount<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, authority: Signer<'info>, } @@ -220,7 +243,9 @@ pub struct FreezeAccount<'info> { #[derive(Accounts)] pub struct ThawAccount<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, authority: Signer<'info>, } @@ -228,9 +253,12 @@ pub struct ThawAccount<'info> { #[derive(Accounts)] pub struct TransferChecked<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, #[account(mut)] + /// CHECK: destination: AccountInfo<'info>, authority: Signer<'info>, } @@ -238,8 +266,11 @@ pub struct TransferChecked<'info> { #[derive(Accounts)] pub struct ApproveChecked<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, + /// CHECK: delegate: AccountInfo<'info>, authority: Signer<'info>, } @@ -247,8 +278,10 @@ pub struct ApproveChecked<'info> { #[derive(Accounts)] pub struct MintToChecked<'info> { #[account(mut)] + /// CHECK: mint: AccountInfo<'info>, #[account(mut)] + /// CHECK: to: AccountInfo<'info>, authority: Signer<'info>, } @@ -256,8 +289,10 @@ pub struct MintToChecked<'info> { #[derive(Accounts)] pub struct BurnChecked<'info> { #[account(mut)] + /// CHECK: source: AccountInfo<'info>, #[account(mut)] + /// CHECK: mint: AccountInfo<'info>, authority: Signer<'info>, } @@ -265,32 +300,40 @@ pub struct BurnChecked<'info> { #[derive(Accounts)] pub struct InitializeAccount2<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, + /// CHECK: rent: AccountInfo<'info>, } #[derive(Accounts)] pub struct SyncNative<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, } #[derive(Accounts)] pub struct InitializeAccount3<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, + /// CHECK: mint: AccountInfo<'info>, } #[derive(Accounts)] pub struct InitializeMultisig2<'info> { #[account(mut)] + /// CHECK: account: AccountInfo<'info>, } #[derive(Accounts)] pub struct InitializeMint2<'info> { #[account(mut)] + /// CHECK: mint: AccountInfo<'info>, } diff --git a/tests/custom-coder/tests/system-coder.ts b/tests/custom-coder/tests/system-coder.ts new file mode 100644 index 0000000000..766fd3a4dd --- /dev/null +++ b/tests/custom-coder/tests/system-coder.ts @@ -0,0 +1,425 @@ +import * as anchor from "@project-serum/anchor"; +import { Native } from "@project-serum/anchor"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { + Keypair, + LAMPORTS_PER_SOL, + NONCE_ACCOUNT_LENGTH, + PublicKey, + SystemProgram, + SYSVAR_RECENT_BLOCKHASHES_PUBKEY, +} from "@solana/web3.js"; +import * as assert from "assert"; +import BN from "bn.js"; + +describe("system-coder", () => { + // Configure the client to use the local cluster. + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + // Client. + const program = Native.system(); + + // Constants. + const aliceKeypair = Keypair.generate(); + + it("Creates an account", async () => { + // arrange + const space = 100; + const lamports = + await program.provider.connection.getMinimumBalanceForRentExemption( + space + ); + const owner = SystemProgram.programId; + // act + await program.methods + .createAccount(new BN(lamports), new BN(space), owner) + .accounts({ + from: provider.wallet.publicKey, + to: aliceKeypair.publicKey, + }) + .signers([aliceKeypair]) + .rpc(); + // assert + const aliceAccount = await program.provider.connection.getAccountInfo( + aliceKeypair.publicKey + ); + assert.notEqual(aliceAccount, null); + assert.ok(owner.equals(aliceAccount.owner)); + assert.equal(lamports, aliceAccount.lamports); + }); + + it("Assigns an account to a program", async () => { + // arrange + const owner = TOKEN_PROGRAM_ID; + // act + await program.methods + .assign(owner) + .accounts({ + pubkey: aliceKeypair.publicKey, + }) + .signers([aliceKeypair]) + .rpc(); + // assert + const aliceAccount = await program.provider.connection.getAccountInfo( + aliceKeypair.publicKey + ); + assert.notEqual(aliceAccount, null); + assert.ok(owner.equals(aliceAccount.owner)); + }); + + it("Allocates space to an account", async () => { + // arrange + const newKeypair = Keypair.generate(); + const space = 100; + const lamports = + await program.provider.connection.getMinimumBalanceForRentExemption( + space + ); + // act + await program.methods + .allocate(new BN(space)) + .accounts({ + pubkey: newKeypair.publicKey, + }) + .postInstructions([ + await program.methods + .transfer(new BN(lamports)) + .accounts({ + from: provider.wallet.publicKey, + to: newKeypair.publicKey, + }) + .instruction(), + ]) + .signers([newKeypair]) + .rpc(); + // assert + const newAccountAfter = await program.provider.connection.getAccountInfo( + newKeypair.publicKey + ); + assert.equal(space, newAccountAfter.data.byteLength); + }); + + it("Creates an account with seed", async () => { + const space = 100; + const lamports = + await program.provider.connection.getMinimumBalanceForRentExemption( + space + ); + const owner = SystemProgram.programId; + const seed = "seeds"; + const bobPublicKey = await PublicKey.createWithSeed( + aliceKeypair.publicKey, + seed, + owner + ); + // act + await program.methods + .createAccountWithSeed( + aliceKeypair.publicKey, + seed, + new BN(lamports), + new BN(space), + owner + ) + .accounts({ + base: aliceKeypair.publicKey, + from: provider.wallet.publicKey, + to: bobPublicKey, + }) + .signers([aliceKeypair]) + .rpc(); + // assert + const bobAccount = await program.provider.connection.getAccountInfo( + bobPublicKey + ); + assert.notEqual(bobAccount, null); + }); + + it("Allocates and assigns an account with seed", async () => { + const owner = TOKEN_PROGRAM_ID; + const seed = "seeds2"; + const space = 100; + const lamports = + await program.provider.connection.getMinimumBalanceForRentExemption( + space + ); + const bobPublicKey = await PublicKey.createWithSeed( + aliceKeypair.publicKey, + seed, + owner + ); + // act + await program.methods + .allocateWithSeed(aliceKeypair.publicKey, seed, new BN(space), owner) + .accounts({ + base: aliceKeypair.publicKey, + account: bobPublicKey, + }) + .postInstructions([ + await program.methods + .transfer(new BN(lamports)) + .accounts({ + from: provider.wallet.publicKey, + to: bobPublicKey, + }) + .instruction(), + await program.methods + .assignWithSeed(aliceKeypair.publicKey, seed, owner) + .accounts({ + base: aliceKeypair.publicKey, + account: bobPublicKey, + }) + .instruction(), + ]) + .signers([aliceKeypair]) + .rpc(); + // assert + const bobAccount = await program.provider.connection.getAccountInfo( + bobPublicKey + ); + assert.notEqual(bobAccount, null); + assert.ok(owner.equals(bobAccount.owner)); + }); + + it("Transfers from account with seed", async () => { + const lamports = 1 * LAMPORTS_PER_SOL; + const owner = SystemProgram.programId; + const seed = "seeds3"; + const bobPublicKey = await PublicKey.createWithSeed( + aliceKeypair.publicKey, + seed, + owner + ); + const aliceAccountBefore = await program.provider.connection.getAccountInfo( + aliceKeypair.publicKey + ); + // act + await program.methods + .transfer(new BN(lamports)) + .accounts({ + from: provider.wallet.publicKey, + to: bobPublicKey, + }) + .rpc(); + await program.methods + .transferWithSeed(new BN(lamports), seed, owner) + .accounts({ + from: bobPublicKey, + base: aliceKeypair.publicKey, + to: aliceKeypair.publicKey, + }) + .signers([aliceKeypair]) + .rpc(); + // assert + const aliceAccountAfter = await program.provider.connection.getAccountInfo( + aliceKeypair.publicKey + ); + assert.equal( + aliceAccountBefore.lamports + lamports, + aliceAccountAfter.lamports + ); + }); + + it("Transfers lamports", async () => { + // arrange + const receiverKeypair = Keypair.generate(); + const lamports = 0.1 * LAMPORTS_PER_SOL; + // act + await program.methods + .transfer(new BN(lamports)) + .accounts({ + from: provider.wallet.publicKey, + to: receiverKeypair.publicKey, + }) + .rpc(); + // assert + const receiverAccount = await program.provider.connection.getAccountInfo( + receiverKeypair.publicKey + ); + assert.notEqual(receiverAccount, null); + assert.equal(lamports, receiverAccount.lamports); + }); + + it("Initializes nonce account", async () => { + // arrange + const nonceKeypair = Keypair.generate(); + const owner = SystemProgram.programId; + const space = NONCE_ACCOUNT_LENGTH; + const lamports = + await provider.connection.getMinimumBalanceForRentExemption(space); + // act + await program.methods + .initializeNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .preInstructions([ + await program.methods + .createAccount(new BN(lamports), new BN(space), owner) + .accounts({ + from: provider.wallet.publicKey, + to: nonceKeypair.publicKey, + }) + .instruction(), + ]) + .signers([nonceKeypair]) + .rpc(); + // assert + const nonceAccount = await program.account.nonce.fetch( + nonceKeypair.publicKey + ); + assert.notEqual(nonceAccount, null); + assert.ok(nonceAccount.authorizedPubkey.equals(provider.wallet.publicKey)); + }); + + it("Advances a nonce account", async () => { + // arrange + const nonceKeypair = Keypair.generate(); + const owner = SystemProgram.programId; + const space = NONCE_ACCOUNT_LENGTH; + const lamports = + await provider.connection.getMinimumBalanceForRentExemption(space); + // act + await program.methods + .initializeNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .preInstructions([ + await program.methods + .createAccount(new BN(lamports), new BN(space), owner) + .accounts({ + from: provider.wallet.publicKey, + to: nonceKeypair.publicKey, + }) + .instruction(), + ]) + .signers([nonceKeypair]) + .rpc(); + // These have to be separate to make sure advance is in another slot. + await program.methods + .advanceNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .rpc(); + // assert + const nonceAccount = await program.account.nonce.fetch( + nonceKeypair.publicKey + ); + assert.notEqual(nonceAccount, null); + }); + + it("Authorizes a nonce account", async () => { + // arrange + const nonceKeypair = Keypair.generate(); + const owner = SystemProgram.programId; + const space = NONCE_ACCOUNT_LENGTH; + const lamports = + await provider.connection.getMinimumBalanceForRentExemption(space); + // act + await program.methods + .initializeNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .preInstructions([ + await program.methods + .createAccount(new BN(lamports), new BN(space), owner) + .accounts({ + from: provider.wallet.publicKey, + to: nonceKeypair.publicKey, + }) + .instruction(), + ]) + .signers([nonceKeypair]) + .rpc(); + await program.methods + .authorizeNonceAccount(aliceKeypair.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + authorized: provider.wallet.publicKey, + }) + .rpc(); + // assert + const nonceAccount = await program.account.nonce.fetch( + nonceKeypair.publicKey + ); + assert.notEqual(nonceAccount, null); + assert.ok(nonceAccount.authorizedPubkey.equals(aliceKeypair.publicKey)); + }); + + it("Withdraws from nonce account", async () => { + // arrange + const nonceKeypair = Keypair.generate(); + const owner = SystemProgram.programId; + const space = NONCE_ACCOUNT_LENGTH; + const lamports = + await provider.connection.getMinimumBalanceForRentExemption(space); + const amount = 0.1 * LAMPORTS_PER_SOL; + const aliceBalanceBefore = ( + await program.provider.connection.getAccountInfo(aliceKeypair.publicKey) + ).lamports; + // act + await program.methods + .initializeNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .preInstructions([ + await program.methods + .createAccount(new BN(lamports), new BN(space), owner) + .accounts({ + from: provider.wallet.publicKey, + to: nonceKeypair.publicKey, + }) + .instruction(), + ]) + .signers([nonceKeypair]) + .rpc(); + await program.methods + .advanceNonceAccount(provider.wallet.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + }) + .postInstructions([ + await program.methods + .transfer(new BN(amount)) + .accounts({ + from: provider.wallet.publicKey, + to: nonceKeypair.publicKey, + }) + .instruction(), + ]) + .rpc(); + await program.methods + .authorizeNonceAccount(aliceKeypair.publicKey) + .accounts({ + nonce: nonceKeypair.publicKey, + authorized: provider.wallet.publicKey, + }) + .rpc(); + await program.methods + .withdrawNonceAccount(new BN(amount)) + .accounts({ + authorized: aliceKeypair.publicKey, + nonce: nonceKeypair.publicKey, + recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + to: aliceKeypair.publicKey, + }) + .signers([aliceKeypair]) + .rpc(); + // assert + const aliceBalanceAfter = ( + await program.provider.connection.getAccountInfo(aliceKeypair.publicKey) + ).lamports; + assert.equal(aliceBalanceAfter - aliceBalanceBefore, amount); + }); +}); diff --git a/ts/src/coder/index.ts b/ts/src/coder/index.ts index 6256103a03..29a15ce164 100644 --- a/ts/src/coder/index.ts +++ b/ts/src/coder/index.ts @@ -3,6 +3,7 @@ import { Event } from "../program/event.js"; export * from "./borsh/index.js"; export * from "./spl-token/index.js"; +export * from "./system/index.js"; /** * Coder provides a facade for encoding and decoding all IDL related objects. diff --git a/ts/src/coder/system/accounts.ts b/ts/src/coder/system/accounts.ts new file mode 100644 index 0000000000..c5749dc3c0 --- /dev/null +++ b/ts/src/coder/system/accounts.ts @@ -0,0 +1,111 @@ +import { AccountsCoder } from "../index.js"; +import { Idl, IdlTypeDef } from "../../idl.js"; +import * as BufferLayout from "buffer-layout"; +import { NONCE_ACCOUNT_LENGTH, PublicKey } from "@solana/web3.js"; +import { accountSize } from "../common.js"; + +export class SystemAccountsCoder + implements AccountsCoder +{ + constructor(private idl: Idl) {} + + public async encode(accountName: A, account: T): Promise { + switch (accountName) { + case "nonce": { + const buffer = Buffer.alloc(NONCE_ACCOUNT_LENGTH); + const len = NONCE_ACCOUNT_LAYOUT.encode(account, buffer); + return buffer.slice(0, len); + } + default: { + throw new Error(`Invalid account name: ${accountName}`); + } + } + } + + public decode(accountName: A, ix: Buffer): T { + return this.decodeUnchecked(accountName, ix); + } + + public decodeUnchecked(accountName: A, ix: Buffer): T { + switch (accountName) { + case "nonce": { + return decodeNonceAccount(ix); + } + default: { + throw new Error(`Invalid account name: ${accountName}`); + } + } + } + + // TODO: this won't use the appendData. + public memcmp(accountName: A, _appendData?: Buffer): any { + switch (accountName) { + case "nonce": { + return { + dataSize: NONCE_ACCOUNT_LENGTH, + }; + } + default: { + throw new Error(`Invalid account name: ${accountName}`); + } + } + } + + public size(idlAccount: IdlTypeDef): number { + return accountSize(this.idl, idlAccount) ?? 0; + } +} + +function decodeNonceAccount(ix: Buffer): T { + return NONCE_ACCOUNT_LAYOUT.decode(ix) as T; +} + +class WrappedLayout extends BufferLayout.Layout { + layout: BufferLayout.Layout; + decoder: (data: T) => U; + encoder: (src: U) => T; + + constructor( + layout: BufferLayout.Layout, + decoder: (data: T) => U, + encoder: (src: U) => T, + property?: string + ) { + super(layout.span, property); + this.layout = layout; + this.decoder = decoder; + this.encoder = encoder; + } + + decode(b: Buffer, offset?: number): U { + return this.decoder(this.layout.decode(b, offset)); + } + + encode(src: U, b: Buffer, offset?: number): number { + return this.layout.encode(this.encoder(src), b, offset); + } + + getSpan(b: Buffer, offset?: number): number { + return this.layout.getSpan(b, offset); + } +} + +function publicKey(property?: string): BufferLayout.Layout { + return new WrappedLayout( + BufferLayout.blob(32), + (b: Buffer) => new PublicKey(b), + (key: PublicKey) => key.toBuffer(), + property + ); +} + +const NONCE_ACCOUNT_LAYOUT = BufferLayout.struct([ + BufferLayout.u32("version"), + BufferLayout.u32("state"), + publicKey("authorizedPubkey"), + publicKey("nonce"), + BufferLayout.struct( + [BufferLayout.nu64("lamportsPerSignature")], + "feeCalculator" + ), +]); diff --git a/ts/src/coder/system/events.ts b/ts/src/coder/system/events.ts new file mode 100644 index 0000000000..9a9f5faac9 --- /dev/null +++ b/ts/src/coder/system/events.ts @@ -0,0 +1,14 @@ +import { EventCoder } from "../index.js"; +import { Idl } from "../../idl.js"; +import { Event } from "../../program/event"; +import { IdlEvent } from "../../idl"; + +export class SystemEventsCoder implements EventCoder { + constructor(_idl: Idl) {} + + decode>( + _log: string + ): Event | null { + throw new Error("System program does not have events"); + } +} diff --git a/ts/src/coder/system/index.ts b/ts/src/coder/system/index.ts new file mode 100644 index 0000000000..be8db86ade --- /dev/null +++ b/ts/src/coder/system/index.ts @@ -0,0 +1,23 @@ +import { Idl } from "../../idl.js"; +import { Coder } from "../index.js"; +import { SystemInstructionCoder } from "./instruction.js"; +import { SystemStateCoder } from "./state.js"; +import { SystemAccountsCoder } from "./accounts.js"; +import { SystemEventsCoder } from "./events.js"; + +/** + * Coder for the System program. + */ +export class SystemCoder implements Coder { + readonly instruction: SystemInstructionCoder; + readonly accounts: SystemAccountsCoder; + readonly state: SystemStateCoder; + readonly events: SystemEventsCoder; + + constructor(idl: Idl) { + this.instruction = new SystemInstructionCoder(idl); + this.accounts = new SystemAccountsCoder(idl); + this.events = new SystemEventsCoder(idl); + this.state = new SystemStateCoder(idl); + } +} diff --git a/ts/src/coder/system/instruction.ts b/ts/src/coder/system/instruction.ts new file mode 100644 index 0000000000..894cdf12c6 --- /dev/null +++ b/ts/src/coder/system/instruction.ts @@ -0,0 +1,317 @@ +import BN from "bn.js"; +import * as BufferLayout from "buffer-layout"; +import camelCase from "camelcase"; +import { Idl } from "../../idl.js"; +import { InstructionCoder } from "../index.js"; + +export class SystemInstructionCoder implements InstructionCoder { + // eslint-disable-next-line @typescript-eslint/no-empty-function + constructor(_: Idl) {} + + encode(ixName: string, ix: any): Buffer { + switch (camelCase(ixName)) { + case "createAccount": { + return encodeCreateAccount(ix); + } + case "assign": { + return encodeAssign(ix); + } + case "transfer": { + return encodeTransfer(ix); + } + case "createAccountWithSeed": { + return encodeCreateAccountWithSeed(ix); + } + case "advanceNonceAccount": { + return encodeAdvanceNonceAccount(ix); + } + case "withdrawNonceAccount": { + return encodeWithdrawNonceAccount(ix); + } + case "initializeNonceAccount": { + return encodeInitializeNonceAccount(ix); + } + case "authorizeNonceAccount": { + return encodeAuthorizeNonceAccount(ix); + } + case "allocate": { + return encodeAllocate(ix); + } + case "allocateWithSeed": { + return encodeAllocateWithSeed(ix); + } + case "assignWithSeed": { + return encodeAssignWithSeed(ix); + } + case "transferWithSeed": { + return encodeTransferWithSeed(ix); + } + default: { + throw new Error(`Invalid instruction: ${ixName}`); + } + } + } + + encodeState(_ixName: string, _ix: any): Buffer { + throw new Error("System does not have state"); + } +} + +class RustStringLayout extends BufferLayout.Layout { + layout = BufferLayout.struct< + Readonly<{ + length?: number; + lengthPadding?: number; + chars: Buffer; + }> + >( + [ + BufferLayout.u32("length"), + BufferLayout.u32("lengthPadding"), + BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), "chars"), + ], + this.property + ); + + constructor(public property?: string) { + super(-1, property); + } + + encode(src: string | null, b: Buffer, offset = 0): number { + if (src === null || src === undefined) { + return this.layout.span; + } + + const data = { + chars: Buffer.from(src, "utf8"), + }; + + return this.layout.encode(data, b, offset); + } + + decode(b: Buffer, offset = 0): string | null { + const data = this.layout.decode(b, offset); + return data["chars"].toString(); + } + + getSpan(b: Buffer, offset = 0): number { + return ( + BufferLayout.u32().span + + BufferLayout.u32().span + + new BN(new Uint8Array(b).slice(offset, offset + 4), 10, "le").toNumber() + ); + } +} + +function rustStringLayout(property: string) { + return new RustStringLayout(property); +} + +function publicKey(property: string): any { + return BufferLayout.blob(32, property); +} + +function encodeCreateAccount({ lamports, space, owner }: any): Buffer { + return encodeData({ + createAccount: { lamports, space, owner: owner.toBuffer() }, + }); +} + +function encodeAssign({ owner }: any): Buffer { + return encodeData({ + assign: { owner: owner.toBuffer() }, + }); +} + +function encodeTransfer({ lamports }: any): Buffer { + return encodeData({ + transfer: { lamports }, + }); +} + +function encodeCreateAccountWithSeed({ + base, + seed, + lamports, + space, + owner, +}: any): Buffer { + return encodeData( + { + createAccountWithSeed: { + base: base.toBuffer(), + seed, + lamports, + space, + owner: owner.toBuffer(), + }, + }, + LAYOUT.getVariant(3).span + seed.length + ); +} + +function encodeInitializeNonceAccount({ authorized }: any): Buffer { + return encodeData({ + initializeNonceAccount: { authorized: authorized.toBuffer() }, + }); +} + +function encodeAdvanceNonceAccount({ authorized }: any): Buffer { + return encodeData({ + advanceNonceAccount: { authorized: authorized.toBuffer() }, + }); +} + +function encodeWithdrawNonceAccount({ lamports }: any): Buffer { + return encodeData({ + withdrawNonceAccount: { lamports }, + }); +} + +function encodeAuthorizeNonceAccount({ authorized }: any): Buffer { + return encodeData({ + authorizeNonceAccount: { authorized: authorized.toBuffer() }, + }); +} + +function encodeAllocate({ space }: any): Buffer { + return encodeData({ + allocate: { space }, + }); +} + +function encodeAllocateWithSeed({ base, seed, space, owner }: any): Buffer { + return encodeData( + { + allocateWithSeed: { + base: base.toBuffer(), + seed, + space, + owner: owner.toBuffer(), + }, + }, + LAYOUT.getVariant(9).span + seed.length + ); +} + +function encodeAssignWithSeed({ base, seed, owner }: any): Buffer { + return encodeData( + { + assignWithSeed: { + base: base.toBuffer(), + seed, + owner: owner.toBuffer(), + }, + }, + LAYOUT.getVariant(10).span + seed.length + ); +} + +function encodeTransferWithSeed({ lamports, seed, owner }: any): Buffer { + return encodeData( + { + transferWithSeed: { + lamports, + seed, + owner: owner.toBuffer(), + }, + }, + LAYOUT.getVariant(11).span + seed.length + ); +} + +const LAYOUT = BufferLayout.union(BufferLayout.u32("instruction")); +LAYOUT.addVariant( + 0, + BufferLayout.struct([ + BufferLayout.ns64("lamports"), + BufferLayout.ns64("space"), + publicKey("owner"), + ]), + "createAccount" +); +LAYOUT.addVariant(1, BufferLayout.struct([publicKey("owner")]), "assign"); +LAYOUT.addVariant( + 2, + BufferLayout.struct([BufferLayout.ns64("lamports")]), + "transfer" +); +LAYOUT.addVariant( + 3, + BufferLayout.struct([ + publicKey("base"), + rustStringLayout("seed"), + BufferLayout.ns64("lamports"), + BufferLayout.ns64("space"), + publicKey("owner"), + ]), + "createAccountWithSeed" +); +LAYOUT.addVariant( + 4, + BufferLayout.struct([publicKey("authorized")]), + "advanceNonceAccount" +); +LAYOUT.addVariant( + 5, + BufferLayout.struct([BufferLayout.ns64("lamports")]), + "withdrawNonceAccount" +); +LAYOUT.addVariant( + 6, + BufferLayout.struct([publicKey("authorized")]), + "initializeNonceAccount" +); +LAYOUT.addVariant( + 7, + BufferLayout.struct([publicKey("authorized")]), + "authorizeNonceAccount" +); +LAYOUT.addVariant( + 8, + BufferLayout.struct([BufferLayout.ns64("space")]), + "allocate" +); +LAYOUT.addVariant( + 9, + BufferLayout.struct([ + publicKey("base"), + rustStringLayout("seed"), + BufferLayout.ns64("space"), + publicKey("owner"), + ]), + "allocateWithSeed" +); +LAYOUT.addVariant( + 10, + BufferLayout.struct([ + publicKey("base"), + rustStringLayout("seed"), + publicKey("owner"), + ]), + "assignWithSeed" +); +LAYOUT.addVariant( + 11, + BufferLayout.struct([ + BufferLayout.ns64("lamports"), + rustStringLayout("seed"), + publicKey("owner"), + ]), + "transferWithSeed" +); + +function encodeData(instruction: any, maxSpan?: number): Buffer { + const b = Buffer.alloc(maxSpan ?? instructionMaxSpan); + const span = LAYOUT.encode(instruction, b); + + if (maxSpan === undefined) { + return b.slice(0, span); + } + + return b; +} + +const instructionMaxSpan = Math.max( + ...Object.values(LAYOUT.registry).map((r: any) => r.span) +); diff --git a/ts/src/coder/system/state.ts b/ts/src/coder/system/state.ts new file mode 100644 index 0000000000..2d7f1a2ef8 --- /dev/null +++ b/ts/src/coder/system/state.ts @@ -0,0 +1,14 @@ +import { StateCoder } from "../index.js"; +import { Idl } from "../../idl"; + +export class SystemStateCoder implements StateCoder { + // eslint-disable-next-line @typescript-eslint/no-empty-function + constructor(_idl: Idl) {} + + encode(_name: string, _account: T): Promise { + throw new Error("System does not have state"); + } + decode(_ix: Buffer): T { + throw new Error("System does not have state"); + } +} diff --git a/ts/src/index.ts b/ts/src/index.ts index 30ecb76112..95dc4120eb 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -16,6 +16,7 @@ export * from "./coder/index.js"; export * as utils from "./utils/index.js"; export * from "./program/index.js"; export * from "./spl/index.js"; +export * from "./native/index.js"; export declare const workspace: any; export declare class Wallet extends NodeWallet {} diff --git a/ts/src/native/index.ts b/ts/src/native/index.ts new file mode 100644 index 0000000000..905c2a0afd --- /dev/null +++ b/ts/src/native/index.ts @@ -0,0 +1,10 @@ +import { Program, Provider } from "../index.js"; +import { program as systemProgram, SystemProgram } from "./system.js"; + +export { SystemProgram } from "./system.js"; + +export class Native { + public static system(provider?: Provider): Program { + return systemProgram(provider); + } +} diff --git a/ts/src/native/system.ts b/ts/src/native/system.ts new file mode 100644 index 0000000000..e42ead4dc9 --- /dev/null +++ b/ts/src/native/system.ts @@ -0,0 +1,781 @@ +import { PublicKey } from "@solana/web3.js"; +import { Program } from "../program/index.js"; +import Provider from "../provider.js"; +import { SystemCoder } from "../coder/system/index.js"; + +const SYSTEM_PROGRAM_ID = new PublicKey("11111111111111111111111111111111"); + +export function program(provider?: Provider): Program { + return new Program(IDL, SYSTEM_PROGRAM_ID, provider, coder()); +} + +export function coder(): SystemCoder { + return new SystemCoder(IDL); +} + +/** + * System IDL. + */ +export type SystemProgram = { + version: "0.1.0"; + name: "system_program"; + instructions: [ + { + name: "createAccount"; + accounts: [ + { + name: "from"; + isMut: true; + isSigner: true; + }, + { + name: "to"; + isMut: true; + isSigner: true; + } + ]; + args: [ + { + name: "lamports"; + type: "u64"; + }, + { + name: "space"; + type: "u64"; + }, + { + name: "owner"; + type: "publicKey"; + } + ]; + }, + { + name: "assign"; + accounts: [ + { + name: "pubkey"; + isMut: true; + isSigner: true; + } + ]; + args: [ + { + name: "owner"; + type: "publicKey"; + } + ]; + }, + { + name: "transfer"; + accounts: [ + { + name: "from"; + isMut: true; + isSigner: true; + }, + { + name: "to"; + isMut: true; + isSigner: false; + } + ]; + args: [ + { + name: "lamports"; + type: "u64"; + } + ]; + }, + { + name: "createAccountWithSeed"; + accounts: [ + { + name: "from"; + isMut: true; + isSigner: true; + }, + { + name: "to"; + isMut: true; + isSigner: false; + }, + { + name: "base"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "base"; + type: "publicKey"; + }, + { + name: "seed"; + type: "string"; + }, + { + name: "lamports"; + type: "u64"; + }, + { + name: "space"; + type: "u64"; + }, + { + name: "owner"; + type: "publicKey"; + } + ]; + }, + { + name: "advanceNonceAccount"; + accounts: [ + { + name: "nonce"; + isMut: true; + isSigner: false; + }, + { + name: "recentBlockhashes"; + isMut: false; + isSigner: false; + }, + { + name: "authorized"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "authorized"; + type: "publicKey"; + } + ]; + }, + { + name: "withdrawNonceAccount"; + accounts: [ + { + name: "nonce"; + isMut: true; + isSigner: false; + }, + { + name: "to"; + isMut: true; + isSigner: false; + }, + { + name: "recentBlockhashes"; + isMut: false; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + }, + { + name: "authorized"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "lamports"; + type: "u64"; + } + ]; + }, + { + name: "initializeNonceAccount"; + accounts: [ + { + name: "nonce"; + isMut: true; + isSigner: true; + }, + { + name: "recentBlockhashes"; + isMut: false; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: "authorized"; + type: "publicKey"; + } + ]; + }, + { + name: "authorizeNonceAccount"; + accounts: [ + { + name: "nonce"; + isMut: true; + isSigner: false; + }, + { + name: "authorized"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "authorized"; + type: "publicKey"; + } + ]; + }, + { + name: "allocate"; + accounts: [ + { + name: "pubkey"; + isMut: true; + isSigner: true; + } + ]; + args: [ + { + name: "space"; + type: "u64"; + } + ]; + }, + { + name: "allocateWithSeed"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "base"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "base"; + type: "publicKey"; + }, + { + name: "seed"; + type: "string"; + }, + { + name: "space"; + type: "u64"; + }, + { + name: "owner"; + type: "publicKey"; + } + ]; + }, + { + name: "assignWithSeed"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "base"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "base"; + type: "publicKey"; + }, + { + name: "seed"; + type: "string"; + }, + { + name: "owner"; + type: "publicKey"; + } + ]; + }, + { + name: "transferWithSeed"; + accounts: [ + { + name: "from"; + isMut: true; + isSigner: false; + }, + { + name: "base"; + isMut: false; + isSigner: true; + }, + { + name: "to"; + isMut: true; + isSigner: false; + } + ]; + args: [ + { + name: "lamports"; + type: "u64"; + }, + { + name: "seed"; + type: "string"; + }, + { + name: "owner"; + type: "publicKey"; + } + ]; + } + ]; + accounts: [ + { + name: "nonce"; + type: { + kind: "struct"; + fields: [ + { + name: "version"; + type: "u32"; + }, + { + name: "state"; + type: "u32"; + }, + { + name: "authorizedPubkey"; + type: "publicKey"; + }, + { + name: "nonce"; + type: "publicKey"; + }, + { + name: "feeCalculator"; + type: { + defined: "FeeCalculator"; + }; + } + ]; + }; + } + ]; + types: [ + { + name: "FeeCalculator"; + type: { + kind: "struct"; + fields: [ + { + name: "lamportsPerSignature"; + type: "u64"; + } + ]; + }; + } + ]; +}; + +export const IDL: SystemProgram = { + version: "0.1.0", + name: "system_program", + instructions: [ + { + name: "createAccount", + accounts: [ + { + name: "from", + isMut: true, + isSigner: true, + }, + { + name: "to", + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: "lamports", + type: "u64", + }, + { + name: "space", + type: "u64", + }, + { + name: "owner", + type: "publicKey", + }, + ], + }, + { + name: "assign", + accounts: [ + { + name: "pubkey", + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: "owner", + type: "publicKey", + }, + ], + }, + { + name: "transfer", + accounts: [ + { + name: "from", + isMut: true, + isSigner: true, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: "lamports", + type: "u64", + }, + ], + }, + { + name: "createAccountWithSeed", + accounts: [ + { + name: "from", + isMut: true, + isSigner: true, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + { + name: "base", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "base", + type: "publicKey", + }, + { + name: "seed", + type: "string", + }, + { + name: "lamports", + type: "u64", + }, + { + name: "space", + type: "u64", + }, + { + name: "owner", + type: "publicKey", + }, + ], + }, + { + name: "advanceNonceAccount", + accounts: [ + { + name: "nonce", + isMut: true, + isSigner: false, + }, + { + name: "recentBlockhashes", + isMut: false, + isSigner: false, + }, + { + name: "authorized", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "authorized", + type: "publicKey", + }, + ], + }, + { + name: "withdrawNonceAccount", + accounts: [ + { + name: "nonce", + isMut: true, + isSigner: false, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + { + name: "recentBlockhashes", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + { + name: "authorized", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "lamports", + type: "u64", + }, + ], + }, + { + name: "initializeNonceAccount", + accounts: [ + { + name: "nonce", + isMut: true, + isSigner: true, + }, + { + name: "recentBlockhashes", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "authorized", + type: "publicKey", + }, + ], + }, + { + name: "authorizeNonceAccount", + accounts: [ + { + name: "nonce", + isMut: true, + isSigner: false, + }, + { + name: "authorized", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "authorized", + type: "publicKey", + }, + ], + }, + { + name: "allocate", + accounts: [ + { + name: "pubkey", + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: "space", + type: "u64", + }, + ], + }, + { + name: "allocateWithSeed", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "base", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "base", + type: "publicKey", + }, + { + name: "seed", + type: "string", + }, + { + name: "space", + type: "u64", + }, + { + name: "owner", + type: "publicKey", + }, + ], + }, + { + name: "assignWithSeed", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "base", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "base", + type: "publicKey", + }, + { + name: "seed", + type: "string", + }, + { + name: "owner", + type: "publicKey", + }, + ], + }, + { + name: "transferWithSeed", + accounts: [ + { + name: "from", + isMut: true, + isSigner: false, + }, + { + name: "base", + isMut: false, + isSigner: true, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: "lamports", + type: "u64", + }, + { + name: "seed", + type: "string", + }, + { + name: "owner", + type: "publicKey", + }, + ], + }, + ], + accounts: [ + { + name: "nonce", + type: { + kind: "struct", + fields: [ + { + name: "version", + type: "u32", + }, + { + name: "state", + type: "u32", + }, + { + name: "authorizedPubkey", + type: "publicKey", + }, + { + name: "nonce", + type: "publicKey", + }, + { + name: "feeCalculator", + type: { + defined: "FeeCalculator", + }, + }, + ], + }, + }, + ], + types: [ + { + name: "FeeCalculator", + type: { + kind: "struct", + fields: [ + { + name: "lamportsPerSignature", + type: "u64", + }, + ], + }, + }, + ], +};