ts: Pubkeys as base58 strings (#304)

This commit is contained in:
Armani Ferrante 2021-05-23 09:58:15 -07:00 committed by GitHub
parent ccf18557f9
commit 8fa867fbd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 76 additions and 36 deletions

View File

@ -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)).

View File

@ -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);
});
});

View File

@ -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,
};

View File

@ -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;

View File

@ -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(

View File

@ -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);

View File

@ -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);

View File

@ -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,
};

View File

@ -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;

View File

@ -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,

View File

@ -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);