diff --git a/CHANGELOG.md b/CHANGELOG.md index d5a599e76b..dbb8fc3e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ incremented for features. * lang: Allow repr overrides for zero copy accounts ([#1273](https://github.com/project-serum/anchor/pull/1273)). +### Breaking + +* ts: `Coder` is now an interface and the existing class has been renamed to `BorshCoder`. This change allows the generation of Anchor clients for non anchor programs ([#1259](https://github.com/project-serum/anchor/pull/1259/files)). + ## [0.20.0] - 2022-01-06 ### Fixes diff --git a/tests/custom-coder/Anchor.toml b/tests/custom-coder/Anchor.toml new file mode 100644 index 0000000000..589023aa22 --- /dev/null +++ b/tests/custom-coder/Anchor.toml @@ -0,0 +1,13 @@ +[programs.localnet] +custom_coder = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" +spl_token = "FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w" + +[registry] +url = "https://anchor.projectserum.com" + +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/tests/custom-coder/Cargo.toml b/tests/custom-coder/Cargo.toml new file mode 100644 index 0000000000..a60de986d3 --- /dev/null +++ b/tests/custom-coder/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = [ + "programs/*" +] diff --git a/tests/custom-coder/migrations/deploy.ts b/tests/custom-coder/migrations/deploy.ts new file mode 100644 index 0000000000..5e3df0dc30 --- /dev/null +++ b/tests/custom-coder/migrations/deploy.ts @@ -0,0 +1,12 @@ +// Migrations are an early feature. Currently, they're nothing more than this +// single deploy script that's invoked from the CLI, injecting a provider +// configured from the workspace's Anchor.toml. + +const anchor = require("@project-serum/anchor"); + +module.exports = async function (provider) { + // Configure client to use the provider. + anchor.setProvider(provider); + + // Add your deploy script here. +}; diff --git a/tests/custom-coder/package.json b/tests/custom-coder/package.json new file mode 100644 index 0000000000..802bc74d8c --- /dev/null +++ b/tests/custom-coder/package.json @@ -0,0 +1,19 @@ +{ + "name": "custom-coder", + "version": "0.20.0", + "license": "(MIT OR Apache-2.0)", + "homepage": "https://github.com/project-serum/anchor#readme", + "bugs": { + "url": "https://github.com/project-serum/anchor/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/project-serum/anchor.git" + }, + "engines": { + "node": ">=11" + }, + "scripts": { + "test": "anchor test" + } +} diff --git a/tests/custom-coder/programs/custom-coder/Cargo.toml b/tests/custom-coder/programs/custom-coder/Cargo.toml new file mode 100644 index 0000000000..12d92d1f14 --- /dev/null +++ b/tests/custom-coder/programs/custom-coder/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "custom-coder" +version = "0.1.0" +description = "Created with Anchor" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "custom_coder" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = "0.20.0" diff --git a/tests/custom-coder/programs/custom-coder/Xargo.toml b/tests/custom-coder/programs/custom-coder/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/custom-coder/programs/custom-coder/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/custom-coder/programs/custom-coder/src/lib.rs b/tests/custom-coder/programs/custom-coder/src/lib.rs new file mode 100644 index 0000000000..fa18cb4123 --- /dev/null +++ b/tests/custom-coder/programs/custom-coder/src/lib.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::*; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[program] +pub mod custom_coder { + use super::*; + pub fn initialize(_ctx: Context, a: Option) -> ProgramResult { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize {} diff --git a/tests/custom-coder/programs/spl-token/Cargo.toml b/tests/custom-coder/programs/spl-token/Cargo.toml new file mode 100644 index 0000000000..e337de613a --- /dev/null +++ b/tests/custom-coder/programs/spl-token/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "spl-token" +version = "0.1.0" +description = "Created with Anchor" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "spl_token" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = "0.20.0" diff --git a/tests/custom-coder/programs/spl-token/Xargo.toml b/tests/custom-coder/programs/spl-token/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/custom-coder/programs/spl-token/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/custom-coder/programs/spl-token/src/lib.rs b/tests/custom-coder/programs/spl-token/src/lib.rs new file mode 100644 index 0000000000..fdd834c3c0 --- /dev/null +++ b/tests/custom-coder/programs/spl-token/src/lib.rs @@ -0,0 +1,296 @@ +use anchor_lang::prelude::*; + +declare_id!("FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w"); + +// This program is simply used to generate the IDL for the token program. +// +// Note that we manually add the COption type to the IDL after +// compiling. +// +#[program] +pub mod spl_token { + use super::*; + + pub fn initialize_mint( + ctx: Context, + decimals: u8, + mint_authority: Pubkey, + // freeze_authority: COption, + ) -> ProgramResult { + Ok(()) + } + + pub fn initialize_account(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn initialize_multisig(ctx: Context, m: u8) -> ProgramResult { + Ok(()) + } + + pub fn transfer(ctx: Context, amount: u64) -> ProgramResult { + Ok(()) + } + + pub fn approve(ctx: Context, amount: u64) -> ProgramResult { + Ok(()) + } + + pub fn revoke(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn set_authority( + ctx: Context, + authority_type: u8, + // new_authority: COption, + ) -> ProgramResult { + Ok(()) + } + + pub fn mint_to(ctx: Context, amount: u64) -> ProgramResult { + Ok(()) + } + + pub fn burn(ctx: Context, amount: u64) -> ProgramResult { + Ok(()) + } + + pub fn close_account(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn freeze_account(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn thaw_account(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn transfer_checked( + ctx: Context, + amount: u64, + decimals: u8, + ) -> ProgramResult { + Ok(()) + } + + pub fn approve_checked( + ctx: Context, + amount: u64, + decimals: u8, + ) -> ProgramResult { + Ok(()) + } + + pub fn mint_to_checked( + ctx: Context, + amount: u64, + decimals: u8, + ) -> ProgramResult { + Ok(()) + } + + pub fn burn_checked(ctx: Context, amount: u64, decimals: u8) -> ProgramResult { + Ok(()) + } + + pub fn initialize_account_2( + ctx: Context, + authority: Pubkey, + ) -> ProgramResult { + Ok(()) + } + + pub fn sync_native(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn initialize_account3( + ctx: Context, + authority: Pubkey, + ) -> ProgramResult { + Ok(()) + } + + pub fn initialize_multisig_2(ctx: Context, m: u8) -> ProgramResult { + Ok(()) + } + + pub fn initialize_mint_2( + ctx: Context, + decimals: u8, + mint_authority: Pubkey, + // freeze_authority: COption, + ) -> ProgramResult { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InitializeMint<'info> { + #[account(mut)] + mint: AccountInfo<'info>, + rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeAccount<'info> { + #[account(mut)] + account: AccountInfo<'info>, + mint: AccountInfo<'info>, + authority: AccountInfo<'info>, + rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMultisig<'info> { + #[account(mut)] + account: AccountInfo<'info>, + rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct Transfer<'info> { + #[account(mut)] + source: AccountInfo<'info>, + #[account(mut)] + destination: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Approve<'info> { + #[account(mut)] + source: AccountInfo<'info>, + delegate: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Revoke<'info> { + #[account(mut)] + source: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct SetAuthority<'info> { + #[account(mut)] + pub mint: AccountInfo<'info>, + pub authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct MintTo<'info> { + #[account(mut)] + pub mint: AccountInfo<'info>, + #[account(mut)] + pub to: AccountInfo<'info>, + pub authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct Burn<'info> { + #[account(mut)] + source: AccountInfo<'info>, + #[account(mut)] + mint: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct CloseAccount<'info> { + #[account(mut)] + account: AccountInfo<'info>, + #[account(mut)] + destination: AccountInfo<'info>, + authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct FreezeAccount<'info> { + #[account(mut)] + account: AccountInfo<'info>, + mint: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct ThawAccount<'info> { + #[account(mut)] + account: AccountInfo<'info>, + mint: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct TransferChecked<'info> { + #[account(mut)] + source: AccountInfo<'info>, + mint: AccountInfo<'info>, + #[account(mut)] + destination: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct ApproveChecked<'info> { + #[account(mut)] + source: AccountInfo<'info>, + mint: AccountInfo<'info>, + delegate: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct MintToChecked<'info> { + #[account(mut)] + mint: AccountInfo<'info>, + #[account(mut)] + to: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct BurnChecked<'info> { + #[account(mut)] + source: AccountInfo<'info>, + #[account(mut)] + mint: AccountInfo<'info>, + authority: Signer<'info>, +} + +#[derive(Accounts)] +pub struct InitializeAccount2<'info> { + #[account(mut)] + account: AccountInfo<'info>, + mint: AccountInfo<'info>, + rent: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct SyncNative<'info> { + #[account(mut)] + account: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeAccount3<'info> { + #[account(mut)] + account: AccountInfo<'info>, + mint: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMultisig2<'info> { + #[account(mut)] + account: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct InitializeMint2<'info> { + #[account(mut)] + mint: AccountInfo<'info>, +} diff --git a/tests/custom-coder/tests/custom-coder.ts b/tests/custom-coder/tests/custom-coder.ts new file mode 100644 index 0000000000..addfd2185c --- /dev/null +++ b/tests/custom-coder/tests/custom-coder.ts @@ -0,0 +1,137 @@ +import * as anchor from "@project-serum/anchor"; +import { Spl } from "@project-serum/anchor"; +import * as assert from "assert"; +import BN from "bn.js"; +import { Keypair, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"; + +describe("custom-coder", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.Provider.env()); + + // Client. + const program = Spl.token(); + + // Constants. + const mintKeypair = Keypair.generate(); + const aliceTokenKeypair = Keypair.generate(); + const bobTokenKeypair = Keypair.generate(); + const rent = SYSVAR_RENT_PUBKEY; + + it("Creates a mint", async () => { + await program.rpc.initializeMint( + 6, + program.provider.wallet.publicKey, + null, + { + accounts: { + mint: mintKeypair.publicKey, + rent, + }, + signers: [mintKeypair], + preInstructions: [ + await program.account.mint.createInstruction(mintKeypair), + ], + } + ); + const mintAccount = await program.account.mint.fetch(mintKeypair.publicKey); + assert.ok( + mintAccount.mintAuthority.equals(program.provider.wallet.publicKey) + ); + assert.ok(mintAccount.freezeAuthority === null); + assert.ok(mintAccount.decimals === 6); + assert.ok(mintAccount.isInitialized); + assert.ok(mintAccount.supply.toNumber() === 0); + }); + + it("Creates a token account for alice", async () => { + await program.rpc.initializeAccount({ + accounts: { + account: aliceTokenKeypair.publicKey, + mint: mintKeypair.publicKey, + authority: program.provider.wallet.publicKey, + rent, + }, + signers: [aliceTokenKeypair], + preInstructions: [ + await program.account.token.createInstruction(aliceTokenKeypair), + ], + }); + const token = await program.account.token.fetch( + aliceTokenKeypair.publicKey + ); + assert.ok(token.authority.equals(program.provider.wallet.publicKey)); + assert.ok(token.mint.equals(mintKeypair.publicKey)); + assert.ok(token.amount.toNumber() === 0); + assert.ok(token.delegate === null); + assert.ok(token.state === 0); + assert.ok(token.isNative === null); + assert.ok(token.delegatedAmount.toNumber() === 0); + assert.ok(token.closeAuthority === null); + }); + + it("Mints a token to alice", async () => { + await program.rpc.mintTo(new BN(2), { + accounts: { + mint: mintKeypair.publicKey, + to: aliceTokenKeypair.publicKey, + authority: program.provider.wallet.publicKey, + }, + }); + + const token = await program.account.token.fetch( + aliceTokenKeypair.publicKey + ); + const mint = await program.account.mint.fetch(mintKeypair.publicKey); + assert.ok(token.amount.toNumber() === 2); + assert.ok(mint.supply.toNumber() === 2); + }); + + it("Creates a token for bob", async () => { + await program.rpc.initializeAccount({ + accounts: { + account: bobTokenKeypair.publicKey, + mint: mintKeypair.publicKey, + authority: program.provider.wallet.publicKey, + rent, + }, + signers: [bobTokenKeypair], + preInstructions: [ + await program.account.token.createInstruction(bobTokenKeypair), + ], + }); + }); + + it("Transfer a token from alice to bob", async () => { + await program.rpc.transfer(new BN(1), { + accounts: { + source: aliceTokenKeypair.publicKey, + destination: bobTokenKeypair.publicKey, + authority: program.provider.wallet.publicKey, + }, + }); + const aliceToken = await program.account.token.fetch( + aliceTokenKeypair.publicKey + ); + const bobToken = await program.account.token.fetch( + bobTokenKeypair.publicKey + ); + assert.ok(aliceToken.amount.toNumber() === 1); + assert.ok(bobToken.amount.toNumber() === 1); + }); + + it("Alice burns a token", async () => { + await program.rpc.burn(new BN(1), { + accounts: { + source: aliceTokenKeypair.publicKey, + mint: mintKeypair.publicKey, + authority: program.provider.wallet.publicKey, + }, + }); + const aliceToken = await program.account.token.fetch( + aliceTokenKeypair.publicKey + ); + const mint = await program.account.mint.fetch(mintKeypair.publicKey); + assert.ok(aliceToken.amount.toNumber() === 0); + assert.ok(mint.supply.toNumber() === 1); + }); +}); diff --git a/tests/custom-coder/tsconfig.json b/tests/custom-coder/tsconfig.json new file mode 100644 index 0000000000..cd5d2e3d06 --- /dev/null +++ b/tests/custom-coder/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} diff --git a/tests/package.json b/tests/package.json index 7dcf127f56..a7c54b9a92 100644 --- a/tests/package.json +++ b/tests/package.json @@ -10,6 +10,7 @@ "cfo", "chat", "composite", + "custom-coder", "errors", "escrow", "events", diff --git a/tests/yarn.lock b/tests/yarn.lock index 06b70a7019..69ce24aeb5 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -50,10 +50,10 @@ snake-case "^3.0.4" toml "^3.0.0" -"@project-serum/anchor@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.19.0.tgz#79f1fbe7c3134860ccbfe458a0e09daf79644885" - integrity sha512-cs0LBmJOrL9eJ8MRNqitnzbpCT5QEzVdJmiIjfNV5YaGn1K9vISR7DtISj3Bdl3KBdLqii4CTw1mpHdi8iXUCg== +"@project-serum/anchor@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.0.tgz#547f5c0ff7e66809fa7118b2e3abd8087b5ec519" + integrity sha512-p1KOiqGBIbNsopMrSVoPwgxR1iPffsdjMNCOysahTPL9whX2CLX9HQCdopHjYaGl7+SdHRuXml6Wahk/wUmC8g== dependencies: "@project-serum/borsh" "^0.2.2" "@solana/web3.js" "^1.17.0" diff --git a/ts/src/coder/accounts.ts b/ts/src/coder/borsh/accounts.ts similarity index 59% rename from ts/src/coder/accounts.ts rename to ts/src/coder/borsh/accounts.ts index 0be7433e84..b12a13ddce 100644 --- a/ts/src/coder/accounts.ts +++ b/ts/src/coder/borsh/accounts.ts @@ -1,9 +1,12 @@ +import bs58 from "bs58"; import { Buffer } from "buffer"; import { Layout } from "buffer-layout"; -import { Idl } from "../idl.js"; -import { IdlCoder } from "./idl.js"; -import { sha256 } from "js-sha256"; import camelcase from "camelcase"; +import { sha256 } from "js-sha256"; +import { Idl, IdlTypeDef } from "../../idl.js"; +import { IdlCoder } from "./idl.js"; +import { AccountsCoder } from "../index.js"; +import { accountSize } from "../common.js"; /** * Number of bytes of the account discriminator. @@ -13,12 +16,18 @@ export const ACCOUNT_DISCRIMINATOR_SIZE = 8; /** * Encodes and decodes account objects. */ -export class AccountsCoder { +export class BorshAccountsCoder + implements AccountsCoder { /** * Maps account type identifier to a layout. */ private accountLayouts: Map; + /** + * IDL whose acconts will be coded. + */ + private idl: Idl; + public constructor(idl: Idl) { if (idl.accounts === undefined) { this.accountLayouts = new Map(); @@ -29,6 +38,7 @@ export class AccountsCoder { }); this.accountLayouts = new Map(layouts); + this.idl = idl; } public async encode(accountName: A, account: T): Promise { @@ -39,11 +49,20 @@ export class AccountsCoder { } const len = layout.encode(account, buffer); let accountData = buffer.slice(0, len); - let discriminator = AccountsCoder.accountDiscriminator(accountName); + let discriminator = BorshAccountsCoder.accountDiscriminator(accountName); return Buffer.concat([discriminator, accountData]); } - public decode(accountName: A, ix: Buffer): T { + public decode(accountName: A, data: Buffer): T { + // Assert the account discriminator is correct. + const discriminator = BorshAccountsCoder.accountDiscriminator(accountName); + if (discriminator.compare(data.slice(0, 8))) { + throw new Error("Invalid account discriminator"); + } + return this.decodeUnchecked(accountName, data); + } + + public decodeUnchecked(accountName: A, ix: Buffer): T { // Chop off the discriminator before decoding. const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE); const layout = this.accountLayouts.get(accountName); @@ -53,6 +72,22 @@ export class AccountsCoder { return layout.decode(data); } + public memcmp(accountName: A, appendData?: Buffer): any { + const discriminator = BorshAccountsCoder.accountDiscriminator(accountName); + return { + offset: 0, + bytes: bs58.encode( + appendData ? Buffer.concat([discriminator, appendData]) : discriminator + ), + }; + } + + public size(idlAccount: IdlTypeDef): number { + return ( + ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(this.idl, idlAccount) ?? 0) + ); + } + /** * Calculates and returns a unique 8 byte discriminator prepended to all anchor accounts. * diff --git a/ts/src/coder/event.ts b/ts/src/coder/borsh/event.ts similarity index 91% rename from ts/src/coder/event.ts rename to ts/src/coder/borsh/event.ts index d98d081fc7..6581c62372 100644 --- a/ts/src/coder/event.ts +++ b/ts/src/coder/borsh/event.ts @@ -2,11 +2,12 @@ import { Buffer } from "buffer"; import * as base64 from "base64-js"; import { Layout } from "buffer-layout"; import { sha256 } from "js-sha256"; -import { Idl, IdlEvent, IdlTypeDef } from "../idl.js"; -import { Event, EventData } from "../program/event.js"; +import { Idl, IdlEvent, IdlTypeDef } from "../../idl.js"; +import { Event, EventData } from "../../program/event.js"; import { IdlCoder } from "./idl.js"; +import { EventCoder } from "../index.js"; -export class EventCoder { +export class BorshEventCoder implements EventCoder { /** * Maps account type identifier to a layout. */ diff --git a/ts/src/coder/idl.ts b/ts/src/coder/borsh/idl.ts similarity index 98% rename from ts/src/coder/idl.ts rename to ts/src/coder/borsh/idl.ts index b1391f05cc..304fe70555 100644 --- a/ts/src/coder/idl.ts +++ b/ts/src/coder/borsh/idl.ts @@ -1,8 +1,8 @@ import camelCase from "camelcase"; import { Layout } from "buffer-layout"; import * as borsh from "@project-serum/borsh"; -import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js"; -import { IdlError } from "../error.js"; +import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../../idl.js"; +import { IdlError } from "../../error.js"; export class IdlCoder { public static fieldLayout( diff --git a/ts/src/coder/borsh/index.ts b/ts/src/coder/borsh/index.ts new file mode 100644 index 0000000000..4b84fd2ede --- /dev/null +++ b/ts/src/coder/borsh/index.ts @@ -0,0 +1,46 @@ +import { Idl } from "../../idl.js"; +import { BorshInstructionCoder } from "./instruction.js"; +import { BorshAccountsCoder } from "./accounts.js"; +import { BorshEventCoder } from "./event.js"; +import { BorshStateCoder } from "./state.js"; +import { Coder } from "../index.js"; + +export { BorshInstructionCoder } from "./instruction.js"; +export { BorshAccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js"; +export { BorshEventCoder, eventDiscriminator } from "./event.js"; +export { BorshStateCoder, stateDiscriminator } from "./state.js"; + +/** + * BorshCoder is the default Coder for Anchor programs implementing the + * borsh based serialization interface. + */ +export class BorshCoder implements Coder { + /** + * Instruction coder. + */ + readonly instruction: BorshInstructionCoder; + + /** + * Account coder. + */ + readonly accounts: BorshAccountsCoder; + + /** + * Coder for state structs. + */ + readonly state: BorshStateCoder; + + /** + * Coder for events. + */ + readonly events: BorshEventCoder; + + constructor(idl: Idl) { + this.instruction = new BorshInstructionCoder(idl); + this.accounts = new BorshAccountsCoder(idl); + this.events = new BorshEventCoder(idl); + if (idl.state) { + this.state = new BorshStateCoder(idl); + } + } +} diff --git a/ts/src/coder/instruction.ts b/ts/src/coder/borsh/instruction.ts similarity index 93% rename from ts/src/coder/instruction.ts rename to ts/src/coder/borsh/instruction.ts index f80ca29729..224ef1937e 100644 --- a/ts/src/coder/instruction.ts +++ b/ts/src/coder/borsh/instruction.ts @@ -1,8 +1,11 @@ +import bs58 from "bs58"; import { Buffer } from "buffer"; -import camelCase from "camelcase"; import { Layout } from "buffer-layout"; +import camelCase from "camelcase"; +import { snakeCase } from "snake-case"; +import { sha256 } from "js-sha256"; import * as borsh from "@project-serum/borsh"; -import bs58 from "bs58"; +import { AccountMeta, PublicKey } from "@solana/web3.js"; import { Idl, IdlField, @@ -16,10 +19,9 @@ import { IdlTypeOption, IdlTypeDefined, IdlAccounts, -} from "../idl"; +} from "../../idl.js"; import { IdlCoder } from "./idl.js"; -import { sighash } from "./common.js"; -import { AccountMeta, PublicKey } from "@solana/web3.js"; +import { InstructionCoder } from "../index.js"; /** * Namespace for state method function signatures. @@ -34,7 +36,7 @@ export const SIGHASH_GLOBAL_NAMESPACE = "global"; /** * Encodes and decodes program instructions. */ -export class InstructionCoder { +export class BorshInstructionCoder implements InstructionCoder { // Instruction args layout. Maps namespaced method private ixLayout: Map; @@ -42,7 +44,7 @@ export class InstructionCoder { private sighashLayouts: Map; public constructor(private idl: Idl) { - this.ixLayout = InstructionCoder.parseIxLayout(idl); + this.ixLayout = BorshInstructionCoder.parseIxLayout(idl); const sighashLayouts = new Map(); idl.instructions.forEach((ix) => { @@ -69,14 +71,14 @@ export class InstructionCoder { /** * Encodes a program instruction. */ - public encode(ixName: string, ix: any) { + public encode(ixName: string, ix: any): Buffer { return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix); } /** * Encodes a program state instruction. */ - public encodeState(ixName: string, ix: any) { + public encodeState(ixName: string, ix: any): Buffer { return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix); } @@ -379,3 +381,11 @@ function sentenceCase(field: string): string { const result = field.replace(/([A-Z])/g, " $1"); return result.charAt(0).toUpperCase() + result.slice(1); } + +// Not technically sighash, since we don't include the arguments, as Rust +// doesn't allow function overloading. +function sighash(nameSpace: string, ixName: string): Buffer { + let name = snakeCase(ixName); + let preimage = `${nameSpace}:${name}`; + return Buffer.from(sha256.digest(preimage)).slice(0, 8); +} diff --git a/ts/src/coder/state.ts b/ts/src/coder/borsh/state.ts similarity index 90% rename from ts/src/coder/state.ts rename to ts/src/coder/borsh/state.ts index 4c02fd571d..b33f7b20db 100644 --- a/ts/src/coder/state.ts +++ b/ts/src/coder/borsh/state.ts @@ -1,11 +1,11 @@ import { Buffer } from "buffer"; import { Layout } from "buffer-layout"; import { sha256 } from "js-sha256"; -import { Idl } from "../idl.js"; +import { Idl } from "../../idl.js"; import { IdlCoder } from "./idl.js"; -import * as features from "../utils/features.js"; +import * as features from "../../utils/features.js"; -export class StateCoder { +export class BorshStateCoder { private layout: Layout; public constructor(idl: Idl) { diff --git a/ts/src/coder/common.ts b/ts/src/coder/common.ts index b221035ca0..8b6e26f989 100644 --- a/ts/src/coder/common.ts +++ b/ts/src/coder/common.ts @@ -1,6 +1,3 @@ -import { Buffer } from "buffer"; -import { snakeCase } from "snake-case"; -import { sha256 } from "js-sha256"; import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js"; import { IdlError } from "../error.js"; @@ -70,6 +67,9 @@ function typeSize(idl: Idl, ty: IdlType): number { if ("option" in ty) { return 1 + typeSize(idl, ty.option); } + if ("coption" in ty) { + return 4 + typeSize(idl, ty.coption); + } if ("defined" in ty) { const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? []; if (filtered.length !== 1) { @@ -87,11 +87,3 @@ function typeSize(idl: Idl, ty: IdlType): number { throw new Error(`Invalid type ${JSON.stringify(ty)}`); } } - -// Not technically sighash, since we don't include the arguments, as Rust -// doesn't allow function overloading. -export function sighash(nameSpace: string, ixName: string): Buffer { - let name = snakeCase(ixName); - let preimage = `${nameSpace}:${name}`; - return Buffer.from(sha256.digest(preimage)).slice(0, 8); -} diff --git a/ts/src/coder/index.ts b/ts/src/coder/index.ts index 2389f106ab..6256103a03 100644 --- a/ts/src/coder/index.ts +++ b/ts/src/coder/index.ts @@ -1,20 +1,13 @@ -import { Idl } from "../idl.js"; -import { InstructionCoder } from "./instruction.js"; -import { AccountsCoder } from "./accounts.js"; -import { EventCoder } from "./event.js"; -import { StateCoder } from "./state.js"; -import { sighash } from "./common.js"; - -export { accountSize } from "./common.js"; -export { InstructionCoder } from "./instruction.js"; -export { AccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js"; -export { EventCoder, eventDiscriminator } from "./event.js"; -export { StateCoder, stateDiscriminator } from "./state.js"; +import { IdlEvent, IdlTypeDef } from "../idl.js"; +import { Event } from "../program/event.js"; + +export * from "./borsh/index.js"; +export * from "./spl-token/index.js"; /** * Coder provides a facade for encoding and decoding all IDL related objects. */ -export default class Coder { +export interface Coder { /** * Instruction coder. */ @@ -23,7 +16,7 @@ export default class Coder { /** * Account coder. */ - readonly accounts: AccountsCoder; + readonly accounts: AccountsCoder; /** * Coder for state structs. @@ -34,17 +27,28 @@ export default class Coder { * Coder for events. */ readonly events: EventCoder; +} + +export interface StateCoder { + encode(name: string, account: T): Promise; + decode(ix: Buffer): T; +} + +export interface AccountsCoder { + encode(accountName: A, account: T): Promise; + decode(accountName: A, ix: Buffer): T; + decodeUnchecked(accountName: A, ix: Buffer): T; + memcmp(accountName: A, appendData?: Buffer): any; + size(idlAccount: IdlTypeDef): number; +} + +export interface InstructionCoder { + encode(ixName: string, ix: any): Buffer; + encodeState(ixName: string, ix: any): Buffer; +} - constructor(idl: Idl) { - this.instruction = new InstructionCoder(idl); - this.accounts = new AccountsCoder(idl); - this.events = new EventCoder(idl); - if (idl.state) { - this.state = new StateCoder(idl); - } - } - - public sighash(nameSpace: string, ixName: string): Buffer { - return sighash(nameSpace, ixName); - } +export interface EventCoder { + decode>( + log: string + ): Event | null; } diff --git a/ts/src/coder/spl-token/accounts.ts b/ts/src/coder/spl-token/accounts.ts new file mode 100644 index 0000000000..a73311f255 --- /dev/null +++ b/ts/src/coder/spl-token/accounts.ts @@ -0,0 +1,96 @@ +import * as BufferLayout from "buffer-layout"; +import { publicKey, uint64, coption, bool } from "./buffer-layout.js"; +import { AccountsCoder } from "../index.js"; +import { Idl, IdlTypeDef } from "../../idl.js"; +import { accountSize } from "../common"; + +export class SplTokenAccountsCoder + implements AccountsCoder { + constructor(private idl: Idl) {} + + public async encode(accountName: A, account: T): Promise { + switch (accountName) { + case "Token": { + const buffer = Buffer.alloc(165); + const len = TOKEN_ACCOUNT_LAYOUT.encode(account, buffer); + return buffer.slice(0, len); + } + case "Mint": { + const buffer = Buffer.alloc(82); + const len = MINT_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 "Token": { + return decodeTokenAccount(ix); + } + case "Mint": { + return decodeMintAccount(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 "Token": { + return { + dataSize: 165, + }; + } + case "Mint": { + return { + dataSize: 82, + }; + } + default: { + throw new Error(`Invalid account name: ${accountName}`); + } + } + } + + public size(idlAccount: IdlTypeDef): number { + return accountSize(this.idl, idlAccount) ?? 0; + } +} + +function decodeMintAccount(ix: Buffer): T { + return MINT_ACCOUNT_LAYOUT.decode(ix) as T; +} + +function decodeTokenAccount(ix: Buffer): T { + return TOKEN_ACCOUNT_LAYOUT.decode(ix) as T; +} + +const MINT_ACCOUNT_LAYOUT = BufferLayout.struct([ + coption(publicKey(), "mintAuthority"), + uint64("supply"), + BufferLayout.u8("decimals"), + bool("isInitialized"), + coption(publicKey(), "freezeAuthority"), +]); + +const TOKEN_ACCOUNT_LAYOUT = BufferLayout.struct([ + publicKey("mint"), + publicKey("authority"), + uint64("amount"), + coption(publicKey(), "delegate"), + BufferLayout.u8("state"), + coption(uint64(), "isNative"), + uint64("delegatedAmount"), + coption(publicKey(), "closeAuthority"), +]); diff --git a/ts/src/coder/spl-token/buffer-layout.ts b/ts/src/coder/spl-token/buffer-layout.ts new file mode 100644 index 0000000000..1abfb20370 --- /dev/null +++ b/ts/src/coder/spl-token/buffer-layout.ts @@ -0,0 +1,151 @@ +import BN from "bn.js"; +import * as BufferLayout from "buffer-layout"; +import { Layout } from "buffer-layout"; +import { PublicKey } from "@solana/web3.js"; + +export function uint64(property?: string): Layout { + return new WrappedLayout( + BufferLayout.blob(8), + (b: Buffer) => u64.fromBuffer(b), + (n: u64) => n.toBuffer(), + property + ); +} + +export function bool(property?: string): Layout { + return new WrappedLayout(BufferLayout.u8(), decodeBool, encodeBool, property); +} + +export function publicKey(property?: string): Layout { + return new WrappedLayout( + BufferLayout.blob(32), + (b: Buffer) => new PublicKey(b), + (key: PublicKey) => key.toBuffer(), + property + ); +} + +export function coption( + layout: Layout, + property?: string +): Layout { + return new COptionLayout(layout, property); +} + +class WrappedLayout extends Layout { + layout: Layout; + decoder: (data: T) => U; + encoder: (src: U) => T; + + constructor( + layout: 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); + } +} + +export class COptionLayout extends Layout { + layout: Layout; + discriminator: Layout; + + constructor(layout: Layout, property?: string) { + super(-1, property); + this.layout = layout; + this.discriminator = BufferLayout.u32(); + } + + encode(src: T | null, b: Buffer, offset = 0): number { + if (src === null || src === undefined) { + return this.discriminator.encode(0, b, offset); + } + this.discriminator.encode(1, b, offset); + return this.layout.encode(src, b, offset + 4) + 4; + } + + decode(b: Buffer, offset = 0): T | null { + const discriminator = b[offset]; + if (discriminator === 0) { + return null; + } else if (discriminator === 1) { + return this.layout.decode(b, offset + 4); + } + throw new Error("Invalid option " + this.property); + } + + getSpan(b: Buffer, offset = 0): number { + const discriminator = b[offset]; + if (discriminator === 0) { + return 1; + } else if (discriminator === 1) { + return this.layout.getSpan(b, offset + 4) + 4; + } + throw new Error("Invalid option " + this.property); + } +} + +function decodeBool(value: number): boolean { + if (value === 0) { + return false; + } else if (value === 1) { + return true; + } + throw new Error("Invalid bool: " + value); +} + +function encodeBool(value: boolean): number { + return value ? 1 : 0; +} + +export class u64 extends BN { + /** + * Convert to Buffer representation + */ + toBuffer(): Buffer { + const a = super.toArray().reverse(); + const b = Buffer.from(a); + if (b.length === 8) { + return b; + } + if (b.length >= 8) { + throw new Error("u64 too large"); + } + + const zeroPad = Buffer.alloc(8); + b.copy(zeroPad); + return zeroPad; + } + + /** + * Construct a u64 from Buffer representation + */ + static fromBuffer(buffer: Buffer): u64 { + if (buffer.length !== 8) { + throw new Error(`Invalid buffer length: ${buffer.length}`); + } + return new u64( + [...buffer] + .reverse() + .map((i) => `00${i.toString(16)}`.slice(-2)) + .join(""), + 16 + ); + } +} diff --git a/ts/src/coder/spl-token/events.ts b/ts/src/coder/spl-token/events.ts new file mode 100644 index 0000000000..973258f4cf --- /dev/null +++ b/ts/src/coder/spl-token/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 SplTokenEventsCoder implements EventCoder { + constructor(_idl: Idl) {} + + decode>( + _log: string + ): Event | null { + throw new Error("SPL token program does not have events"); + } +} diff --git a/ts/src/coder/spl-token/index.ts b/ts/src/coder/spl-token/index.ts new file mode 100644 index 0000000000..188b32b287 --- /dev/null +++ b/ts/src/coder/spl-token/index.ts @@ -0,0 +1,23 @@ +import { Idl } from "../../idl.js"; +import { Coder } from "../index.js"; +import { SplTokenInstructionCoder } from "./instruction.js"; +import { SplTokenStateCoder } from "./state.js"; +import { SplTokenAccountsCoder } from "./accounts.js"; +import { SplTokenEventsCoder } from "./events.js"; + +/** + * Coder for the SPL token program. + */ +export class SplTokenCoder implements Coder { + readonly instruction: SplTokenInstructionCoder; + readonly accounts: SplTokenAccountsCoder; + readonly state: SplTokenStateCoder; + readonly events: SplTokenEventsCoder; + + constructor(idl: Idl) { + this.instruction = new SplTokenInstructionCoder(idl); + this.accounts = new SplTokenAccountsCoder(idl); + this.events = new SplTokenEventsCoder(idl); + this.state = new SplTokenStateCoder(idl); + } +} diff --git a/ts/src/coder/spl-token/instruction.ts b/ts/src/coder/spl-token/instruction.ts new file mode 100644 index 0000000000..06dfe017f2 --- /dev/null +++ b/ts/src/coder/spl-token/instruction.ts @@ -0,0 +1,349 @@ +import * as BufferLayout from "buffer-layout"; +import camelCase from "camelcase"; +import { PublicKey } from "@solana/web3.js"; +import { InstructionCoder } from "../index.js"; +import { Idl } from "../../idl.js"; + +export class SplTokenInstructionCoder implements InstructionCoder { + constructor(_: Idl) {} + + encode(ixName: string, ix: any): Buffer { + switch (camelCase(ixName)) { + case "initializeMint": { + return encodeInitializeMint(ix); + } + case "initializeAccount": { + return encodeInitializeAccount(ix); + } + case "initializeMultisig": { + return encodeInitializeMultisig(ix); + } + case "transfer": { + return encodeTransfer(ix); + } + case "approve": { + return encodeApprove(ix); + } + case "revoke": { + return encodeRevoke(ix); + } + case "setAuthority": { + return encodeSetAuthority(ix); + } + case "mintTo": { + return encodeMintTo(ix); + } + case "burn": { + return encodeBurn(ix); + } + case "closeAccount": { + return encodeCloseAccount(ix); + } + case "freezeAccount": { + return encodeFreezeAccount(ix); + } + case "thawAccount": { + return encodeThawAccount(ix); + } + case "transferChecked": { + return encodeTransferChecked(ix); + } + case "approvedChecked": { + return encodeApproveChecked(ix); + } + case "mintToChecked": { + return encodeMintToChecked(ix); + } + case "burnChecked": { + return encodeBurnChecked(ix); + } + case "intializeAccount2": { + return encodeInitializeAccount2(ix); + } + case "syncNative": { + return encodeSyncNative(ix); + } + case "initializeAccount3": { + return encodeInitializeAccount3(ix); + } + case "initializeMultisig2": { + return encodeInitializeMultisig2(ix); + } + case "initializeMint2": { + return encodeInitializeMint2(ix); + } + default: { + throw new Error(`Invalid instruction: ${ixName}`); + } + } + } + + encodeState(_ixName: string, _ix: any): Buffer { + throw new Error("SPL token does not have state"); + } +} + +function encodeInitializeMint({ + decimals, + mintAuthority, + freezeAuthority, +}: any): Buffer { + return encodeData({ + initializeMint: { + decimals, + mintAuthority: mintAuthority.toBuffer(), + freezeAuthorityOption: !!freezeAuthority, + freezeAuthority: (freezeAuthority || PublicKey.default).toBuffer(), + }, + }); +} + +function encodeInitializeAccount(_ix: any): Buffer { + return encodeData({ + initializeAccount: {}, + }); +} + +function encodeInitializeMultisig({ m }: any): Buffer { + return encodeData({ + initializeMultisig: { + m, + }, + }); +} + +function encodeTransfer({ amount }: any): Buffer { + return encodeData({ + transfer: { amount }, + }); +} + +function encodeApprove({ amount }: any): Buffer { + return encodeData({ + approve: { amount }, + }); +} + +function encodeRevoke(_ix: any): Buffer { + return encodeData({ + revoke: {}, + }); +} + +function encodeSetAuthority({ authorityType, newAuthority }: any): Buffer { + return encodeData({ + setAuthority: { authorityType, newAuthority }, + }); +} + +function encodeMintTo({ amount }: any): Buffer { + return encodeData({ + mintTo: { amount }, + }); +} + +function encodeBurn({ amount }: any): Buffer { + return encodeData({ + burn: { amount }, + }); +} + +function encodeCloseAccount(_: any): Buffer { + return encodeData({ + closeAccount: {}, + }); +} + +function encodeFreezeAccount(_: any): Buffer { + return encodeData({ + freezeAccount: {}, + }); +} + +function encodeThawAccount(_: any): Buffer { + return encodeData({ + thawAccount: {}, + }); +} + +function encodeTransferChecked({ amount, decimals }: any): Buffer { + return encodeData({ + transferChecked: { amount, decimals }, + }); +} + +function encodeApproveChecked({ amount, decimals }: any): Buffer { + return encodeData({ + approveChecked: { amount, decimals }, + }); +} + +function encodeMintToChecked({ amount, decimals }: any): Buffer { + return encodeData({ + mintToChecked: { amount, decimals }, + }); +} + +function encodeBurnChecked({ amount, decimals }: any): Buffer { + return encodeData({ + burnChecked: { amount, decimals }, + }); +} + +function encodeInitializeAccount2({ authority }: any): Buffer { + return encodeData({ + initilaizeAccount2: { authority }, + }); +} + +function encodeSyncNative(_: any): Buffer { + return encodeData({ + syncNative: {}, + }); +} + +function encodeInitializeAccount3({ authority }: any): Buffer { + return encodeData({ + initializeAccount3: { authority }, + }); +} + +function encodeInitializeMultisig2({ m }: any): Buffer { + return encodeData({ + initializeMultisig2: { m }, + }); +} + +function encodeInitializeMint2({ + decimals, + mintAuthority, + freezeAuthority, +}: any): Buffer { + return encodeData({ + encodeInitializeMint2: { decimals, mintAuthority, freezeAuthority }, + }); +} + +const LAYOUT = BufferLayout.union(BufferLayout.u8("instruction")); +LAYOUT.addVariant( + 0, + BufferLayout.struct([ + BufferLayout.u8("decimals"), + BufferLayout.blob(32, "mintAuthority"), + BufferLayout.u8("freezeAuthorityOption"), + publicKey("freezeAuthority"), + ]), + "initializeMint" +); +LAYOUT.addVariant(1, BufferLayout.struct([]), "initializeAccount"); +LAYOUT.addVariant( + 2, + BufferLayout.struct([BufferLayout.u8("m")]), + "initializeMultisig" +); +LAYOUT.addVariant( + 3, + BufferLayout.struct([BufferLayout.nu64("amount")]), + "transfer" +); +LAYOUT.addVariant( + 4, + BufferLayout.struct([BufferLayout.nu64("amount")]), + "approve" +); +LAYOUT.addVariant(5, BufferLayout.struct([]), "revoke"); +LAYOUT.addVariant( + 6, + BufferLayout.struct([ + BufferLayout.u8("authorityType"), + BufferLayout.u8("newAuthorityOption"), + publicKey("newAuthority"), + ]), + "setAuthority" +); +LAYOUT.addVariant( + 7, + BufferLayout.struct([BufferLayout.nu64("amount")]), + "mintTo" +); +LAYOUT.addVariant( + 8, + BufferLayout.struct([BufferLayout.nu64("amount")]), + "burn" +); +LAYOUT.addVariant(9, BufferLayout.struct([]), "closeAccount"); +LAYOUT.addVariant(10, BufferLayout.struct([]), "freezeAccount"); +LAYOUT.addVariant(11, BufferLayout.struct([]), "thawAccount"); +LAYOUT.addVariant( + 12, + BufferLayout.struct([ + BufferLayout.nu64("amount"), + BufferLayout.u8("decimals"), + ]), + "transferChecked" +); +LAYOUT.addVariant( + 13, + BufferLayout.struct([ + BufferLayout.nu64("amount"), + BufferLayout.u8("decimals"), + ]), + "approvedChecked" +); +LAYOUT.addVariant( + 14, + BufferLayout.struct([ + BufferLayout.nu64("amount"), + BufferLayout.u8("decimals"), + ]), + "mintToChecked" +); +LAYOUT.addVariant( + 15, + BufferLayout.struct([ + BufferLayout.nu64("amount"), + BufferLayout.u8("decimals"), + ]), + "burnedChecked" +); +LAYOUT.addVariant( + 16, + BufferLayout.struct([publicKey("authority")]), + "InitializeAccount2" +); +LAYOUT.addVariant(17, BufferLayout.struct([]), "syncNative"); +LAYOUT.addVariant( + 18, + BufferLayout.struct([publicKey("authority")]), + "initializeAccount3" +); +LAYOUT.addVariant( + 19, + BufferLayout.struct([BufferLayout.u8("m")]), + "initializeMultisig2" +); +LAYOUT.addVariant( + 20, + BufferLayout.struct([ + BufferLayout.u8("decimals"), + publicKey("mintAuthority"), + BufferLayout.u8("freezeAuthorityOption"), + publicKey("freezeAuthority"), + ]), + "initializeMint2" +); + +function publicKey(property: string): any { + return BufferLayout.blob(32, property); +} + +function encodeData(instruction: any): Buffer { + let b = Buffer.alloc(instructionMaxSpan); + let span = LAYOUT.encode(instruction, b); + return b.slice(0, span); +} + +const instructionMaxSpan = Math.max( + // @ts-ignore + ...Object.values(LAYOUT.registry).map((r) => r.span) +); diff --git a/ts/src/coder/spl-token/state.ts b/ts/src/coder/spl-token/state.ts new file mode 100644 index 0000000000..baff6a8a91 --- /dev/null +++ b/ts/src/coder/spl-token/state.ts @@ -0,0 +1,13 @@ +import { StateCoder } from "../index.js"; +import { Idl } from "../../idl"; + +export class SplTokenStateCoder implements StateCoder { + constructor(_idl: Idl) {} + + encode(_name: string, _account: T): Promise { + throw new Error("SPL token does not have state"); + } + decode(_ix: Buffer): T { + throw new Error("SPL token does not have state"); + } +} diff --git a/ts/src/idl.ts b/ts/src/idl.ts index bb1831bf79..7bc65fc634 100644 --- a/ts/src/idl.ts +++ b/ts/src/idl.ts @@ -99,6 +99,7 @@ export type IdlType = | "publicKey" | IdlTypeDefined | IdlTypeOption + | IdlTypeCOption | IdlTypeVec | IdlTypeArray; @@ -111,6 +112,10 @@ export type IdlTypeOption = { option: IdlType; }; +export type IdlTypeCOption = { + coption: IdlType; +}; + export type IdlTypeVec = { vec: IdlType; }; diff --git a/ts/src/index.ts b/ts/src/index.ts index 35fc903fc3..f359ef86f6 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -3,20 +3,13 @@ import { isBrowser } from "./utils/common.js"; export { default as BN } from "bn.js"; export * as web3 from "@solana/web3.js"; export { default as Provider, getProvider, setProvider } from "./provider.js"; -export { - default as Coder, - InstructionCoder, - EventCoder, - StateCoder, - AccountsCoder, -} from "./coder/index.js"; - export * from "./error.js"; -export { Instruction } from "./coder/instruction.js"; +export { Instruction } from "./coder/borsh/instruction.js"; export { Idl } from "./idl.js"; - +export * from "./coder/index.js"; export * as utils from "./utils/index.js"; export * from "./program/index.js"; +export * from "./spl/index.js"; export declare const workspace: any; export declare const Wallet: import("./nodewallet").default; diff --git a/ts/src/program/event.ts b/ts/src/program/event.ts index ef3f5fcf67..fa80ba5dc7 100644 --- a/ts/src/program/event.ts +++ b/ts/src/program/event.ts @@ -1,7 +1,7 @@ import { PublicKey } from "@solana/web3.js"; import * as assert from "assert"; import { IdlEvent, IdlEventField } from "../idl.js"; -import Coder from "../coder/index.js"; +import { Coder } from "../coder/index.js"; import { DecodeType } from "./namespace/types.js"; import Provider from "../provider.js"; diff --git a/ts/src/program/index.ts b/ts/src/program/index.ts index 4561ddf171..6e6f06bc65 100644 --- a/ts/src/program/index.ts +++ b/ts/src/program/index.ts @@ -2,7 +2,7 @@ import { inflate } from "pako"; import { PublicKey } from "@solana/web3.js"; import Provider, { getProvider } from "../provider.js"; import { Idl, idlAddress, decodeIdlAccount } from "../idl.js"; -import Coder from "../coder/index.js"; +import { Coder, BorshCoder } from "../coder/index.js"; import NamespaceFactory, { RpcNamespace, InstructionNamespace, @@ -249,7 +249,12 @@ export class Program { * @param provider The network and wallet context to use. If not provided * then uses [[getProvider]]. */ - public constructor(idl: IDL, programId: Address, provider?: Provider) { + public constructor( + idl: IDL, + programId: Address, + provider?: Provider, + coder?: Coder + ) { programId = translateAddress(programId); if (!provider) { @@ -260,7 +265,7 @@ export class Program { this._idl = idl; this._provider = provider; this._programId = programId; - this._coder = new Coder(idl); + this._coder = coder ?? new BorshCoder(idl); this._events = new EventManager(this._programId, provider, this._coder); // Dynamic namespaces. diff --git a/ts/src/program/namespace/account.ts b/ts/src/program/namespace/account.ts index f166f8f36c..69341c58f7 100644 --- a/ts/src/program/namespace/account.ts +++ b/ts/src/program/namespace/account.ts @@ -1,7 +1,5 @@ -import { Buffer } from "buffer"; import camelCase from "camelcase"; import EventEmitter from "eventemitter3"; -import bs58 from "bs58"; import { Signer, PublicKey, @@ -13,11 +11,7 @@ import { } from "@solana/web3.js"; import Provider, { getProvider } from "../../provider.js"; import { Idl, IdlTypeDef } from "../../idl.js"; -import Coder, { - ACCOUNT_DISCRIMINATOR_SIZE, - accountSize, - AccountsCoder, -} from "../../coder/index.js"; +import { Coder, BorshCoder } from "../../coder/index.js"; import { Subscription, Address, translateAddress } from "../common.js"; import { AllAccountsMap, IdlTypes, TypeDef } from "./types.js"; import * as pubkeyUtil from "../../utils/pubkey.js"; @@ -126,9 +120,8 @@ export class AccountClient< this._idlAccount = idlAccount; this._programId = programId; this._provider = provider ?? getProvider(); - this._coder = coder ?? new Coder(idl); - this._size = - ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(idl, idlAccount) ?? 0); + this._coder = coder ?? new BorshCoder(idl); + this._size = this._coder.accounts.size(idlAccount); } /** @@ -144,15 +137,6 @@ export class AccountClient< if (accountInfo === null) { return null; } - - // Assert the account discriminator is correct. - const discriminator = AccountsCoder.accountDiscriminator( - this._idlAccount.name - ); - if (discriminator.compare(accountInfo.data.slice(0, 8))) { - throw new Error("Invalid account discriminator"); - } - return this._coder.accounts.decode( this._idlAccount.name, accountInfo.data @@ -188,17 +172,11 @@ export class AccountClient< commitment ); - const discriminator = AccountsCoder.accountDiscriminator( - this._idlAccount.name - ); // Decode accounts where discriminator is correct, null otherwise return accounts.map((account) => { if (account == null) { return null; } - if (discriminator.compare(account?.account.data.slice(0, 8))) { - return null; - } return this._coder.accounts.decode( this._idlAccount.name, account?.account.data @@ -223,24 +201,16 @@ export class AccountClient< async all( filters?: Buffer | GetProgramAccountsFilter[] ): Promise[]> { - const discriminator = AccountsCoder.accountDiscriminator( - this._idlAccount.name - ); - let resp = await this._provider.connection.getProgramAccounts( this._programId, { commitment: this._provider.connection.commitment, filters: [ { - memcmp: { - offset: 0, - bytes: bs58.encode( - filters instanceof Buffer - ? Buffer.concat([discriminator, filters]) - : discriminator - ), - }, + memcmp: this.coder.accounts.memcmp( + this._idlAccount.name, + filters instanceof Buffer ? filters : undefined + ), }, ...(Array.isArray(filters) ? filters : []), ], diff --git a/ts/src/program/namespace/index.ts b/ts/src/program/namespace/index.ts index 467f1748f9..53b34100dc 100644 --- a/ts/src/program/namespace/index.ts +++ b/ts/src/program/namespace/index.ts @@ -1,8 +1,8 @@ import camelCase from "camelcase"; import { PublicKey } from "@solana/web3.js"; -import Coder from "../../coder/index.js"; +import { Coder } from "../../coder/index.js"; import Provider from "../../provider.js"; -import { Idl, IdlInstruction } from "../../idl.js"; +import { Idl } from "../../idl.js"; import StateFactory, { StateClient } from "./state.js"; import InstructionFactory, { InstructionNamespace } from "./instruction.js"; import TransactionFactory, { TransactionNamespace } from "./transaction.js"; diff --git a/ts/src/program/namespace/simulate.ts b/ts/src/program/namespace/simulate.ts index 362b4b9ce4..e15dfd985d 100644 --- a/ts/src/program/namespace/simulate.ts +++ b/ts/src/program/namespace/simulate.ts @@ -7,7 +7,7 @@ import Provider from "../../provider.js"; import { splitArgsAndCtx } from "../context.js"; import { TransactionFn } from "./transaction.js"; import { EventParser, Event } from "../event.js"; -import Coder from "../../coder/index.js"; +import { Coder } from "../../coder/index.js"; import { Idl, IdlEvent } from "../../idl.js"; import { ProgramError } from "../../error.js"; import * as features from "../../utils/features.js"; diff --git a/ts/src/program/namespace/state.ts b/ts/src/program/namespace/state.ts index 68da547ca8..2e3bc6f468 100644 --- a/ts/src/program/namespace/state.ts +++ b/ts/src/program/namespace/state.ts @@ -8,7 +8,7 @@ import { } from "@solana/web3.js"; import Provider, { getProvider } from "../../provider.js"; import { Idl, IdlInstruction, IdlStateMethod, IdlTypeDef } from "../../idl.js"; -import Coder, { stateDiscriminator } from "../../coder/index.js"; +import { BorshCoder, Coder, stateDiscriminator } from "../../coder/index.js"; import { RpcNamespace, InstructionNamespace, @@ -87,7 +87,7 @@ export class StateClient { /** * Returns the coder. */ - public readonly coder: Coder = new Coder(idl) + public readonly coder: Coder = new BorshCoder(idl) ) { this._idl = idl; this._programId = programId; diff --git a/ts/src/spl/index.ts b/ts/src/spl/index.ts new file mode 100644 index 0000000000..7ab707bb51 --- /dev/null +++ b/ts/src/spl/index.ts @@ -0,0 +1,10 @@ +import { Program } from "../index.js"; +import { program as tokenProgram, SplToken } from "./token.js"; + +export { SplToken } from "./token.js"; + +export class Spl { + public static token(): Program { + return tokenProgram(); + } +} diff --git a/ts/src/spl/token.ts b/ts/src/spl/token.ts new file mode 100644 index 0000000000..eefc83670e --- /dev/null +++ b/ts/src/spl/token.ts @@ -0,0 +1,1230 @@ +import { PublicKey } from "@solana/web3.js"; +import { Program } from "../program/index.js"; +import Provider from "../provider.js"; +import { SplTokenCoder } from "../coder/spl-token/index.js"; + +const TOKEN_PROGRAM_ID = new PublicKey( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" +); + +export function program(provider?: Provider): Program { + return new Program( + IDL, + TOKEN_PROGRAM_ID, + provider, + new SplTokenCoder(IDL) + ); +} + +/** + * SplToken IDL. + */ +export type SplToken = { + version: "0.1.0"; + name: "spl_token"; + instructions: [ + { + name: "initializeMint"; + accounts: [ + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: "decimals"; + type: "u8"; + }, + { + name: "mintAuthority"; + type: "publicKey"; + }, + { + name: "freezeAuthority"; + type: { + coption: "publicKey"; + }; + } + ]; + }, + { + name: "initializeAccount"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + } + ]; + args: []; + }, + { + name: "initializeMultisig"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: "m"; + type: "u8"; + } + ]; + }, + { + name: "transfer"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "destination"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + } + ]; + }, + { + name: "approve"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "delegate"; + isMut: false; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + } + ]; + }, + { + name: "revoke"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: []; + }, + { + name: "setAuthority"; + accounts: [ + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "authorityType"; + type: "u8"; + }, + { + name: "newAuthority"; + type: { + coption: "publicKey"; + }; + } + ]; + }, + { + name: "mintTo"; + accounts: [ + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "to"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + } + ]; + }, + { + name: "burn"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + } + ]; + }, + { + name: "closeAccount"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "destination"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: false; + } + ]; + args: []; + }, + { + name: "freezeAccount"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: []; + }, + { + name: "thawAccount"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: []; + }, + { + name: "transferChecked"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "destination"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "decimals"; + type: "u8"; + } + ]; + }, + { + name: "approveChecked"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "delegate"; + isMut: false; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "decimals"; + type: "u8"; + } + ]; + }, + { + name: "mintToChecked"; + accounts: [ + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "to"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "decimals"; + type: "u8"; + } + ]; + }, + { + name: "burnChecked"; + accounts: [ + { + name: "source"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: true; + isSigner: false; + }, + { + name: "authority"; + isMut: false; + isSigner: true; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "decimals"; + type: "u8"; + } + ]; + }, + { + name: "initializeAccount2"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + }, + { + name: "rent"; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: "authority"; + type: "publicKey"; + } + ]; + }, + { + name: "syncNative"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + } + ]; + args: []; + }, + { + name: "initializeAccount3"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + }, + { + name: "mint"; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: "authority"; + type: "publicKey"; + } + ]; + }, + { + name: "initializeMultisig2"; + accounts: [ + { + name: "account"; + isMut: true; + isSigner: false; + } + ]; + args: [ + { + name: "m"; + type: "u8"; + } + ]; + }, + { + name: "initializeMint2"; + accounts: [ + { + name: "mint"; + isMut: true; + isSigner: false; + } + ]; + args: [ + { + name: "decimals"; + type: "u8"; + }, + { + name: "mintAuthority"; + type: "publicKey"; + }, + { + name: "freezeAuthority"; + type: { + coption: "publicKey"; + }; + } + ]; + } + ]; + accounts: [ + { + name: "Mint"; + type: { + kind: "struct"; + fields: [ + { + name: "mintAuthority"; + type: { + coption: "publicKey"; + }; + }, + { + name: "supply"; + type: "u64"; + }, + { + name: "decimals"; + type: "u8"; + }, + { + name: "isInitialized"; + type: "bool"; + }, + { + name: "freezeAuthority"; + type: { + coption: "publicKey"; + }; + } + ]; + }; + }, + { + name: "Token"; + type: { + kind: "struct"; + fields: [ + { + name: "mint"; + type: "publicKey"; + }, + { + name: "authority"; + type: "publicKey"; + }, + { + name: "amount"; + type: "u64"; + }, + { + name: "delegate"; + type: { + coption: "publicKey"; + }; + }, + { + name: "state"; + type: "u8"; + }, + { + name: "isNative"; + type: { + coption: "u64"; + }; + }, + { + name: "delegatedAmount"; + type: "u64"; + }, + { + name: "closeAuthority"; + type: { + coption: "publicKey"; + }; + } + ]; + }; + } + ]; +}; + +export const IDL: SplToken = { + version: "0.1.0", + name: "spl_token", + instructions: [ + { + name: "initializeMint", + accounts: [ + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "decimals", + type: "u8", + }, + { + name: "mintAuthority", + type: "publicKey", + }, + { + name: "freezeAuthority", + type: { + coption: "publicKey", + }, + }, + ], + }, + { + name: "initializeAccount", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: "initializeMultisig", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "m", + type: "u8", + }, + ], + }, + { + name: "transfer", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "destination", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + ], + }, + { + name: "approve", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "delegate", + isMut: false, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + ], + }, + { + name: "revoke", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: "setAuthority", + accounts: [ + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "authorityType", + type: "u8", + }, + { + name: "newAuthority", + type: { + coption: "publicKey", + }, + }, + ], + }, + { + name: "mintTo", + accounts: [ + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + ], + }, + { + name: "burn", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + ], + }, + { + name: "closeAccount", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "destination", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: "freezeAccount", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: "thawAccount", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: "transferChecked", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "destination", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + { + name: "decimals", + type: "u8", + }, + ], + }, + { + name: "approveChecked", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "delegate", + isMut: false, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + { + name: "decimals", + type: "u8", + }, + ], + }, + { + name: "mintToChecked", + accounts: [ + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "to", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + { + name: "decimals", + type: "u8", + }, + ], + }, + { + name: "burnChecked", + accounts: [ + { + name: "source", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + { + name: "decimals", + type: "u8", + }, + ], + }, + { + name: "initializeAccount2", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "authority", + type: "publicKey", + }, + ], + }, + { + name: "syncNative", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + ], + args: [], + }, + { + name: "initializeAccount3", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + { + name: "mint", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "authority", + type: "publicKey", + }, + ], + }, + { + name: "initializeMultisig2", + accounts: [ + { + name: "account", + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: "m", + type: "u8", + }, + ], + }, + { + name: "initializeMint2", + accounts: [ + { + name: "mint", + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: "decimals", + type: "u8", + }, + { + name: "mintAuthority", + type: "publicKey", + }, + { + name: "freezeAuthority", + type: { + coption: "publicKey", + }, + }, + ], + }, + ], + accounts: [ + { + name: "Mint", + type: { + kind: "struct", + fields: [ + { + name: "mintAuthority", + type: { + coption: "publicKey", + }, + }, + { + name: "supply", + type: "u64", + }, + { + name: "decimals", + type: "u8", + }, + { + name: "isInitialized", + type: "bool", + }, + { + name: "freezeAuthority", + type: { + coption: "publicKey", + }, + }, + ], + }, + }, + { + name: "Token", + type: { + kind: "struct", + fields: [ + { + name: "mint", + type: "publicKey", + }, + { + name: "authority", + type: "publicKey", + }, + { + name: "amount", + type: "u64", + }, + { + name: "delegate", + type: { + coption: "publicKey", + }, + }, + { + name: "state", + type: "u8", + }, + { + name: "isNative", + type: { + coption: "u64", + }, + }, + { + name: "delegatedAmount", + type: "u64", + }, + { + name: "closeAuthority", + type: { + coption: "publicKey", + }, + }, + ], + }, + }, + ], +}; diff --git a/ts/tests/events.spec.ts b/ts/tests/events.spec.ts index 85f13f67e6..5e8456adb0 100644 --- a/ts/tests/events.spec.ts +++ b/ts/tests/events.spec.ts @@ -1,6 +1,6 @@ import { PublicKey } from "@solana/web3.js"; import { EventParser } from "../src/program/event"; -import { Coder } from "../src"; +import { BorshCoder } from "../src"; describe("Events", () => { it("Parses multiple instructions", async () => { @@ -22,7 +22,7 @@ describe("Events", () => { }, ], }; - const coder = new Coder(idl); + const coder = new BorshCoder(idl); const programId = PublicKey.default; const eventParser = new EventParser(programId, coder); diff --git a/ts/tests/transaction.spec.ts b/ts/tests/transaction.spec.ts index 403124815d..4a3e69aea1 100644 --- a/ts/tests/transaction.spec.ts +++ b/ts/tests/transaction.spec.ts @@ -1,6 +1,6 @@ import TransactionFactory from "../src/program/namespace/transaction"; import InstructionFactory from "../src/program/namespace/instruction"; -import { Coder } from "../src"; +import { BorshCoder } from "../src"; import { PublicKey, TransactionInstruction } from "@solana/web3.js"; describe("Transaction", () => { @@ -27,7 +27,7 @@ describe("Transaction", () => { }; it("should add pre instructions before method ix", async () => { - const coder = new Coder(idl); + const coder = new BorshCoder(idl); const programId = PublicKey.default; const ixItem = InstructionFactory.build( idl.instructions[0], @@ -41,7 +41,7 @@ describe("Transaction", () => { }); it("should add post instructions after method ix", async () => { - const coder = new Coder(idl); + const coder = new BorshCoder(idl); const programId = PublicKey.default; const ixItem = InstructionFactory.build( idl.instructions[0], @@ -55,7 +55,7 @@ describe("Transaction", () => { }); it("should throw error if both preInstructions and instructions are used", async () => { - const coder = new Coder(idl); + const coder = new BorshCoder(idl); const programId = PublicKey.default; const ixItem = InstructionFactory.build( idl.instructions[0],