ts: Pubkeys as base58 strings (#304)
This commit is contained in:
parent
ccf18557f9
commit
8fa867fbd6
|
@ -14,6 +14,7 @@ incremented for features.
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
|
* ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
|
||||||
|
* ts: Introduce `Address` type, allowing one to use Base 58 encoded strings in public APIs ([#304](https://github.com/project-serum/anchor/pull/304)).
|
||||||
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
|
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
|
||||||
* cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
|
* cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
|
||||||
* cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).
|
* cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).
|
||||||
|
|
|
@ -207,6 +207,8 @@ describe("misc", () => {
|
||||||
assert.ok(dataAccount.data === -3);
|
assert.ok(dataAccount.data === -3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let dataPubkey;
|
||||||
|
|
||||||
it("Can use i16 in the idl", async () => {
|
it("Can use i16 in the idl", async () => {
|
||||||
const data = anchor.web3.Keypair.generate();
|
const data = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.testI16(-2048, {
|
await program.rpc.testI16(-2048, {
|
||||||
|
@ -219,5 +221,12 @@ describe("misc", () => {
|
||||||
});
|
});
|
||||||
const dataAccount = await program.account.dataI16(data.publicKey);
|
const dataAccount = await program.account.dataI16(data.publicKey);
|
||||||
assert.ok(dataAccount.data === -2048);
|
assert.ok(dataAccount.data === -2048);
|
||||||
|
|
||||||
|
dataPubkey = data.publicKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Can use base58 strings to fetch an account", async () => {
|
||||||
|
const dataAccount = await program.account.dataI16(dataPubkey.toString());
|
||||||
|
assert.ok(dataAccount.data === -2048);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Idl } from "./idl";
|
||||||
import workspace from "./workspace";
|
import workspace from "./workspace";
|
||||||
import utils from "./utils";
|
import utils from "./utils";
|
||||||
import { Program } from "./program";
|
import { Program } from "./program";
|
||||||
|
import { Address } from "./program/common";
|
||||||
import { ProgramAccount } from "./program/namespace";
|
import { ProgramAccount } from "./program/namespace";
|
||||||
import { Context, Accounts } from "./program/context";
|
import { Context, Accounts } from "./program/context";
|
||||||
|
|
||||||
|
@ -37,4 +38,5 @@ export {
|
||||||
Idl,
|
Idl,
|
||||||
utils,
|
utils,
|
||||||
Wallet,
|
Wallet,
|
||||||
|
Address,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
|
import * as bs58 from "bs58";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
|
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
|
||||||
import { ProgramError } from "../error";
|
import { ProgramError } from "../error";
|
||||||
import { Accounts } from "./context";
|
import { Accounts } from "./context";
|
||||||
|
import Provider from "../provider";
|
||||||
|
|
||||||
export type Subscription = {
|
export type Subscription = {
|
||||||
listener: number;
|
listener: number;
|
||||||
|
@ -77,3 +80,19 @@ export function translateError(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translates an address to a Pubkey.
|
||||||
|
export function translateAddress(address: Address): PublicKey {
|
||||||
|
if (typeof address === "string") {
|
||||||
|
const pk = new PublicKey(address);
|
||||||
|
return pk;
|
||||||
|
} else {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An address to identify an account on chain. Can be a [[PublicKey]],
|
||||||
|
* or Base 58 encoded string.
|
||||||
|
*/
|
||||||
|
export type Address = PublicKey | string;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
AccountMeta,
|
AccountMeta,
|
||||||
Signer,
|
Signer,
|
||||||
PublicKey,
|
|
||||||
ConfirmOptions,
|
ConfirmOptions,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
import { Address } from "./common";
|
||||||
import { IdlInstruction } from "../idl";
|
import { IdlInstruction } from "../idl";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +56,7 @@ export type Context = {
|
||||||
* nested here.
|
* nested here.
|
||||||
*/
|
*/
|
||||||
export type Accounts = {
|
export type Accounts = {
|
||||||
[key: string]: PublicKey | Accounts;
|
[key: string]: Address | Accounts;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function splitArgsAndCtx(
|
export function splitArgsAndCtx(
|
||||||
|
|
|
@ -14,6 +14,7 @@ import NamespaceFactory, {
|
||||||
import { getProvider } from "../";
|
import { getProvider } from "../";
|
||||||
import { decodeUtf8 } from "../utils";
|
import { decodeUtf8 } from "../utils";
|
||||||
import { EventParser } from "./event";
|
import { EventParser } from "./event";
|
||||||
|
import { Address, translateAddress } from "./common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Program
|
* ## Program
|
||||||
|
@ -78,19 +79,19 @@ export class Program {
|
||||||
* ## account
|
* ## account
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.account.<account>(publicKey);
|
* program.account.<account>(address);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ## Parameters
|
* ## Parameters
|
||||||
*
|
*
|
||||||
* 1. `publicKey` - The [[PublicKey]] of the account.
|
* 1. `address` - The [[Address]] of the account.
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
* To fetch a `Counter` object from the above example,
|
* To fetch a `Counter` object from the above example,
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* const counter = await program.account.counter(publicKey);
|
* const counter = await program.account.counter(address);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
readonly account: AccountNamespace;
|
readonly account: AccountNamespace;
|
||||||
|
@ -233,7 +234,9 @@ export class Program {
|
||||||
* @param provider The network and wallet context to use. If not provided
|
* @param provider The network and wallet context to use. If not provided
|
||||||
* then uses [[getProvider]].
|
* then uses [[getProvider]].
|
||||||
*/
|
*/
|
||||||
public constructor(idl: Idl, programId: PublicKey, provider?: Provider) {
|
public constructor(idl: Idl, programId: Address, provider?: Provider) {
|
||||||
|
programId = translateAddress(programId);
|
||||||
|
|
||||||
// Fields.
|
// Fields.
|
||||||
this._idl = idl;
|
this._idl = idl;
|
||||||
this._programId = programId;
|
this._programId = programId;
|
||||||
|
@ -266,7 +269,9 @@ export class Program {
|
||||||
* @param programId The on-chain address of the program.
|
* @param programId The on-chain address of the program.
|
||||||
* @param provider The network and wallet context.
|
* @param provider The network and wallet context.
|
||||||
*/
|
*/
|
||||||
public static async at(programId: PublicKey, provider?: Provider) {
|
public static async at(address: Address, provider?: Provider) {
|
||||||
|
const programId = translateAddress(address);
|
||||||
|
|
||||||
const idl = await Program.fetchIdl(programId, provider);
|
const idl = await Program.fetchIdl(programId, provider);
|
||||||
return new Program(idl, programId, provider);
|
return new Program(idl, programId, provider);
|
||||||
}
|
}
|
||||||
|
@ -280,10 +285,12 @@ export class Program {
|
||||||
* @param programId The on-chain address of the program.
|
* @param programId The on-chain address of the program.
|
||||||
* @param provider The network and wallet context.
|
* @param provider The network and wallet context.
|
||||||
*/
|
*/
|
||||||
public static async fetchIdl(programId: PublicKey, provider?: Provider) {
|
public static async fetchIdl(address: Address, provider?: Provider) {
|
||||||
provider = provider ?? getProvider();
|
provider = provider ?? getProvider();
|
||||||
const address = await idlAddress(programId);
|
const programId = translateAddress(address);
|
||||||
const accountInfo = await provider.connection.getAccountInfo(address);
|
|
||||||
|
const idlAddr = await idlAddress(programId);
|
||||||
|
const accountInfo = await provider.connection.getAccountInfo(idlAddr);
|
||||||
// Chop off account discriminator.
|
// Chop off account discriminator.
|
||||||
let idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
|
let idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
|
||||||
const inflatedIdl = inflate(idlAccount.data);
|
const inflatedIdl = inflate(idlAccount.data);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import Coder, {
|
||||||
accountDiscriminator,
|
accountDiscriminator,
|
||||||
accountSize,
|
accountSize,
|
||||||
} from "../../coder";
|
} from "../../coder";
|
||||||
import { Subscription } from "../common";
|
import { Subscription, Address, translateAddress } from "../common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts is a dynamically generated object to fetch any given account
|
* Accounts is a dynamically generated object to fetch any given account
|
||||||
|
@ -36,8 +36,8 @@ export type AccountFn<T = any> = AccountProps & ((address: PublicKey) => T);
|
||||||
type AccountProps = {
|
type AccountProps = {
|
||||||
size: number;
|
size: number;
|
||||||
all: (filter?: Buffer) => Promise<ProgramAccount<any>[]>;
|
all: (filter?: Buffer) => Promise<ProgramAccount<any>[]>;
|
||||||
subscribe: (address: PublicKey, commitment?: Commitment) => EventEmitter;
|
subscribe: (address: Address, commitment?: Commitment) => EventEmitter;
|
||||||
unsubscribe: (address: PublicKey) => void;
|
unsubscribe: (address: Address) => void;
|
||||||
createInstruction: (signer: Signer) => Promise<TransactionInstruction>;
|
createInstruction: (signer: Signer) => Promise<TransactionInstruction>;
|
||||||
associated: (...args: PublicKey[]) => Promise<any>;
|
associated: (...args: PublicKey[]) => Promise<any>;
|
||||||
associatedAddress: (...args: PublicKey[]) => Promise<PublicKey>;
|
associatedAddress: (...args: PublicKey[]) => Promise<PublicKey>;
|
||||||
|
@ -70,8 +70,10 @@ export default class AccountFactory {
|
||||||
const name = camelCase(idlAccount.name);
|
const name = camelCase(idlAccount.name);
|
||||||
|
|
||||||
// Fetches the decoded account from the network.
|
// Fetches the decoded account from the network.
|
||||||
const accountsNamespace = async (address: PublicKey): Promise<any> => {
|
const accountsNamespace = async (address: Address): Promise<any> => {
|
||||||
const accountInfo = await provider.connection.getAccountInfo(address);
|
const accountInfo = await provider.connection.getAccountInfo(
|
||||||
|
translateAddress(address)
|
||||||
|
);
|
||||||
if (accountInfo === null) {
|
if (accountInfo === null) {
|
||||||
throw new Error(`Account does not exist ${address.toString()}`);
|
throw new Error(`Account does not exist ${address.toString()}`);
|
||||||
}
|
}
|
||||||
|
@ -113,14 +115,15 @@ export default class AccountFactory {
|
||||||
// Subscribes to all changes to this account.
|
// Subscribes to all changes to this account.
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
accountsNamespace["subscribe"] = (
|
accountsNamespace["subscribe"] = (
|
||||||
address: PublicKey,
|
address: Address,
|
||||||
commitment?: Commitment
|
commitment?: Commitment
|
||||||
): EventEmitter => {
|
): EventEmitter => {
|
||||||
if (subscriptions.get(address.toString())) {
|
if (subscriptions.get(address.toString())) {
|
||||||
return subscriptions.get(address.toString()).ee;
|
return subscriptions.get(address.toString()).ee;
|
||||||
}
|
}
|
||||||
const ee = new EventEmitter();
|
|
||||||
|
|
||||||
|
const ee = new EventEmitter();
|
||||||
|
address = translateAddress(address);
|
||||||
const listener = provider.connection.onAccountChange(
|
const listener = provider.connection.onAccountChange(
|
||||||
address,
|
address,
|
||||||
(acc) => {
|
(acc) => {
|
||||||
|
@ -140,7 +143,7 @@ export default class AccountFactory {
|
||||||
|
|
||||||
// Unsubscribes to account changes.
|
// Unsubscribes to account changes.
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
accountsNamespace["unsubscribe"] = (address: PublicKey) => {
|
accountsNamespace["unsubscribe"] = (address: Address) => {
|
||||||
let sub = subscriptions.get(address.toString());
|
let sub = subscriptions.get(address.toString());
|
||||||
if (!sub) {
|
if (!sub) {
|
||||||
console.warn("Address is not subscribed");
|
console.warn("Address is not subscribed");
|
||||||
|
@ -200,11 +203,11 @@ export default class AccountFactory {
|
||||||
// Function returning the associated address. Args are keys to associate.
|
// Function returning the associated address. Args are keys to associate.
|
||||||
// Order matters.
|
// Order matters.
|
||||||
accountsNamespace["associatedAddress"] = async (
|
accountsNamespace["associatedAddress"] = async (
|
||||||
...args: PublicKey[]
|
...args: Address[]
|
||||||
): Promise<PublicKey> => {
|
): Promise<PublicKey> => {
|
||||||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
||||||
args.forEach((arg) => {
|
args.forEach((arg) => {
|
||||||
seeds.push(arg.toBuffer());
|
seeds.push(translateAddress(arg).toBuffer());
|
||||||
});
|
});
|
||||||
const [assoc] = await PublicKey.findProgramAddress(seeds, programId);
|
const [assoc] = await PublicKey.findProgramAddress(seeds, programId);
|
||||||
return assoc;
|
return assoc;
|
||||||
|
@ -213,7 +216,7 @@ export default class AccountFactory {
|
||||||
// Function returning the associated account. Args are keys to associate.
|
// Function returning the associated account. Args are keys to associate.
|
||||||
// Order matters.
|
// Order matters.
|
||||||
accountsNamespace["associated"] = async (
|
accountsNamespace["associated"] = async (
|
||||||
...args: PublicKey[]
|
...args: Address[]
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const addr = await accountsNamespace["associatedAddress"](...args);
|
const addr = await accountsNamespace["associatedAddress"](...args);
|
||||||
return await accountsNamespace(addr);
|
return await accountsNamespace(addr);
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||||
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
|
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
|
||||||
import { IdlError } from "../../error";
|
import { IdlError } from "../../error";
|
||||||
import Coder from "../../coder";
|
import Coder from "../../coder";
|
||||||
import { toInstruction, validateAccounts } from "../common";
|
import {
|
||||||
|
toInstruction,
|
||||||
|
validateAccounts,
|
||||||
|
translateAddress,
|
||||||
|
Address,
|
||||||
|
} from "../common";
|
||||||
import { Accounts, splitArgsAndCtx } from "../context";
|
import { Accounts, splitArgsAndCtx } from "../context";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +86,7 @@ export default class InstructionNamespaceFactory {
|
||||||
} else {
|
} else {
|
||||||
const account: IdlAccount = acc as IdlAccount;
|
const account: IdlAccount = acc as IdlAccount;
|
||||||
return {
|
return {
|
||||||
pubkey: ctx[acc.name],
|
pubkey: translateAddress(ctx[acc.name] as Address),
|
||||||
isWritable: account.isMut,
|
isWritable: account.isMut,
|
||||||
isSigner: account.isSigner,
|
isSigner: account.isSigner,
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class RpcFactory {
|
||||||
): RpcFn {
|
): RpcFn {
|
||||||
const rpc = async (...args: any[]): Promise<TransactionSignature> => {
|
const rpc = async (...args: any[]): Promise<TransactionSignature> => {
|
||||||
const tx = txFn(...args);
|
const tx = txFn(...args);
|
||||||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||||
try {
|
try {
|
||||||
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
||||||
return txSig;
|
return txSig;
|
||||||
|
|
|
@ -27,8 +27,8 @@ export type StateNamespace = () =>
|
||||||
address: () => Promise<PublicKey>;
|
address: () => Promise<PublicKey>;
|
||||||
rpc: RpcNamespace;
|
rpc: RpcNamespace;
|
||||||
instruction: InstructionNamespace;
|
instruction: InstructionNamespace;
|
||||||
subscribe: (address: PublicKey, commitment?: Commitment) => EventEmitter;
|
subscribe: (commitment?: Commitment) => EventEmitter;
|
||||||
unsubscribe: (address: PublicKey) => void;
|
unsubscribe: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class StateFactory {
|
export default class StateFactory {
|
||||||
|
@ -92,7 +92,7 @@ export default class StateFactory {
|
||||||
ix[m.name] = ixFn;
|
ix[m.name] = ixFn;
|
||||||
|
|
||||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
||||||
const [_, ctx] = splitArgsAndCtx(m, [...args]);
|
const [, ctx] = splitArgsAndCtx(m, [...args]);
|
||||||
const tx = new Transaction();
|
const tx = new Transaction();
|
||||||
if (ctx.instructions !== undefined) {
|
if (ctx.instructions !== undefined) {
|
||||||
tx.add(...ctx.instructions);
|
tx.add(...ctx.instructions);
|
||||||
|
@ -164,10 +164,7 @@ export default class StateFactory {
|
||||||
|
|
||||||
// Calculates the deterministic address of the program's "state" account.
|
// Calculates the deterministic address of the program's "state" account.
|
||||||
async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
|
async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
|
||||||
let [registrySigner, _nonce] = await PublicKey.findProgramAddress(
|
let [registrySigner] = await PublicKey.findProgramAddress([], programId);
|
||||||
[],
|
|
||||||
programId
|
|
||||||
);
|
|
||||||
return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
|
return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,10 +178,7 @@ async function stateInstructionKeys(
|
||||||
) {
|
) {
|
||||||
if (m.name === "new") {
|
if (m.name === "new") {
|
||||||
// Ctor `new` method.
|
// Ctor `new` method.
|
||||||
const [programSigner, _nonce] = await PublicKey.findProgramAddress(
|
const [programSigner] = await PublicKey.findProgramAddress([], programId);
|
||||||
[],
|
|
||||||
programId
|
|
||||||
);
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
pubkey: provider.wallet.publicKey,
|
pubkey: provider.wallet.publicKey,
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default class TransactionFactory {
|
||||||
// Builds the transaction namespace.
|
// Builds the transaction namespace.
|
||||||
public static build(idlIx: IdlInstruction, ixFn: IxFn): TxFn {
|
public static build(idlIx: IdlInstruction, ixFn: IxFn): TxFn {
|
||||||
const txFn = (...args: any[]): Transaction => {
|
const txFn = (...args: any[]): Transaction => {
|
||||||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||||
const tx = new Transaction();
|
const tx = new Transaction();
|
||||||
if (ctx.instructions !== undefined) {
|
if (ctx.instructions !== undefined) {
|
||||||
tx.add(...ctx.instructions);
|
tx.add(...ctx.instructions);
|
||||||
|
|
Loading…
Reference in New Issue