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