@project-serum/anchor
Advanced tools
Comparing version 0.5.0 to 0.5.1-beta.1
@@ -33,4 +33,2 @@ "use strict"; | ||
Object.defineProperty(exports, "Wallet", { enumerable: true, get: function () { return provider_1.NodeWallet; } }); | ||
const program_1 = require("./program"); | ||
Object.defineProperty(exports, "Program", { enumerable: true, get: function () { return program_1.Program; } }); | ||
const coder_1 = __importDefault(require("./coder")); | ||
@@ -42,2 +40,4 @@ exports.Coder = coder_1.default; | ||
exports.utils = utils_1.default; | ||
const program_1 = require("./program"); | ||
Object.defineProperty(exports, "Program", { enumerable: true, get: function () { return program_1.Program; } }); | ||
let _provider = null; | ||
@@ -44,0 +44,0 @@ function setProvider(provider) { |
@@ -21,8 +21,11 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Program = void 0; | ||
const pako_1 = require("pako"); | ||
const rpc_1 = require("./rpc"); | ||
const idl_1 = require("./idl"); | ||
const coder_1 = __importStar(require("./coder")); | ||
const namespace_1 = __importDefault(require("./namespace")); | ||
const _1 = require("./"); | ||
@@ -42,3 +45,3 @@ const base64 = __importStar(require("base64-js")); | ||
// Build the dynamic RPC functions. | ||
const [rpcs, ixs, txs, accounts, state] = rpc_1.RpcFactory.build(idl, coder, programId, this.provider); | ||
const [rpcs, ixs, txs, accounts, state] = namespace_1.default.build(idl, coder, programId, this.provider); | ||
this.rpc = rpcs; | ||
@@ -45,0 +48,0 @@ this.instruction = ixs; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RpcFactory = void 0; | ||
const camelcase_1 = __importDefault(require("camelcase")); | ||
const eventemitter3_1 = __importDefault(require("eventemitter3")); | ||
const bs58 = __importStar(require("bs58")); | ||
const web3_js_1 = require("@solana/web3.js"); | ||
exports.translateError = void 0; | ||
const error_1 = require("./error"); | ||
const coder_1 = require("./coder"); | ||
// Tracks all subscriptions. | ||
const subscriptions = new Map(); | ||
/** | ||
* RpcFactory builds an Rpcs object for a given IDL. | ||
*/ | ||
class RpcFactory { | ||
/** | ||
* build dynamically generates RPC methods. | ||
* | ||
* @returns an object with all the RPC methods attached. | ||
*/ | ||
static build(idl, coder, programId, provider) { | ||
const idlErrors = parseIdlErrors(idl); | ||
const rpcs = {}; | ||
const ixFns = {}; | ||
const txFns = {}; | ||
const state = RpcFactory.buildState(idl, coder, programId, idlErrors, provider); | ||
idl.instructions.forEach((idlIx) => { | ||
const name = camelcase_1.default(idlIx.name); | ||
// Function to create a raw `TransactionInstruction`. | ||
const ix = RpcFactory.buildIx(idlIx, coder, programId); | ||
// Ffnction to create a `Transaction`. | ||
const tx = RpcFactory.buildTx(idlIx, ix); | ||
// Function to invoke an RPC against a cluster. | ||
const rpc = RpcFactory.buildRpc(idlIx, tx, idlErrors, provider); | ||
rpcs[name] = rpc; | ||
ixFns[name] = ix; | ||
txFns[name] = tx; | ||
}); | ||
const accountFns = idl.accounts | ||
? RpcFactory.buildAccounts(idl, coder, programId, provider) | ||
: {}; | ||
return [rpcs, ixFns, txFns, accountFns, state]; | ||
} | ||
// Builds the state namespace. | ||
static buildState(idl, coder, programId, idlErrors, provider) { | ||
if (idl.state === undefined) { | ||
return undefined; | ||
} | ||
// Fetches the state object from the blockchain. | ||
const state = async () => { | ||
const addr = await programStateAddress(programId); | ||
const accountInfo = await provider.connection.getAccountInfo(addr); | ||
if (accountInfo === null) { | ||
throw new Error(`Account does not exist ${addr.toString()}`); | ||
} | ||
// Assert the account discriminator is correct. | ||
const expectedDiscriminator = await coder_1.stateDiscriminator(idl.state.struct.name); | ||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) { | ||
throw new Error("Invalid account discriminator"); | ||
} | ||
return coder.state.decode(accountInfo.data); | ||
}; | ||
// Namespace with all rpc functions. | ||
const rpc = {}; | ||
const ix = {}; | ||
idl.state.methods.forEach((m) => { | ||
const accounts = async (accounts) => { | ||
const keys = await stateInstructionKeys(programId, provider, m, accounts); | ||
return keys.concat(RpcFactory.accountsArray(accounts, m.accounts)); | ||
}; | ||
const ixFn = async (...args) => { | ||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]); | ||
return new web3_js_1.TransactionInstruction({ | ||
keys: await accounts(ctx.accounts), | ||
programId, | ||
data: coder.instruction.encodeState(m.name, toInstruction(m, ...ixArgs)), | ||
}); | ||
}; | ||
ixFn["accounts"] = accounts; | ||
ix[m.name] = ixFn; | ||
rpc[m.name] = async (...args) => { | ||
const [_, ctx] = splitArgsAndCtx(m, [...args]); | ||
const tx = new web3_js_1.Transaction(); | ||
if (ctx.instructions !== undefined) { | ||
tx.add(...ctx.instructions); | ||
} | ||
tx.add(await ix[m.name](...args)); | ||
try { | ||
const txSig = await provider.send(tx, ctx.signers, ctx.options); | ||
return txSig; | ||
} | ||
catch (err) { | ||
let translatedErr = translateError(idlErrors, err); | ||
if (translatedErr === null) { | ||
throw err; | ||
} | ||
throw translatedErr; | ||
} | ||
}; | ||
}); | ||
state["rpc"] = rpc; | ||
state["instruction"] = ix; | ||
// Calculates the address of the program's global state object account. | ||
state["address"] = async () => programStateAddress(programId); | ||
// Subscription singleton. | ||
let sub = null; | ||
// Subscribe to account changes. | ||
state["subscribe"] = (commitment) => { | ||
if (sub !== null) { | ||
return sub.ee; | ||
} | ||
const ee = new eventemitter3_1.default(); | ||
state["address"]().then((address) => { | ||
const listener = provider.connection.onAccountChange(address, (acc) => { | ||
const account = coder.state.decode(acc.data); | ||
ee.emit("change", account); | ||
}, commitment); | ||
sub = { | ||
ee, | ||
listener, | ||
}; | ||
}); | ||
return ee; | ||
}; | ||
// Unsubscribe from account changes. | ||
state["unsubscribe"] = () => { | ||
if (sub !== null) { | ||
provider.connection | ||
.removeAccountChangeListener(sub.listener) | ||
.then(async () => { | ||
sub = null; | ||
}) | ||
.catch(console.error); | ||
} | ||
}; | ||
return state; | ||
} | ||
// Builds the instuction namespace. | ||
static buildIx(idlIx, coder, programId) { | ||
if (idlIx.name === "_inner") { | ||
throw new error_1.IdlError("the _inner name is reserved"); | ||
} | ||
const ix = (...args) => { | ||
const [ixArgs, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
validateAccounts(idlIx.accounts, ctx.accounts); | ||
validateInstruction(idlIx, ...args); | ||
const keys = RpcFactory.accountsArray(ctx.accounts, idlIx.accounts); | ||
if (ctx.remainingAccounts !== undefined) { | ||
keys.push(...ctx.remainingAccounts); | ||
} | ||
if (ctx.__private && ctx.__private.logAccounts) { | ||
console.log("Outgoing account metas:", keys); | ||
} | ||
return new web3_js_1.TransactionInstruction({ | ||
keys, | ||
programId, | ||
data: coder.instruction.encode(idlIx.name, toInstruction(idlIx, ...ixArgs)), | ||
}); | ||
}; | ||
// Utility fn for ordering the accounts for this instruction. | ||
ix["accounts"] = (accs) => { | ||
return RpcFactory.accountsArray(accs, idlIx.accounts); | ||
}; | ||
return ix; | ||
} | ||
static accountsArray(ctx, accounts) { | ||
return accounts | ||
.map((acc) => { | ||
// Nested accounts. | ||
// @ts-ignore | ||
const nestedAccounts = acc.accounts; | ||
if (nestedAccounts !== undefined) { | ||
const rpcAccs = ctx[acc.name]; | ||
return RpcFactory.accountsArray(rpcAccs, nestedAccounts).flat(); | ||
} | ||
else { | ||
const account = acc; | ||
return { | ||
pubkey: ctx[acc.name], | ||
isWritable: account.isMut, | ||
isSigner: account.isSigner, | ||
}; | ||
} | ||
}) | ||
.flat(); | ||
} | ||
// Builds the rpc namespace. | ||
static buildRpc(idlIx, txFn, idlErrors, provider) { | ||
const rpc = async (...args) => { | ||
const tx = txFn(...args); | ||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
try { | ||
const txSig = await provider.send(tx, ctx.signers, ctx.options); | ||
return txSig; | ||
} | ||
catch (err) { | ||
console.log("Translating error", err); | ||
let translatedErr = translateError(idlErrors, err); | ||
if (translatedErr === null) { | ||
throw err; | ||
} | ||
throw translatedErr; | ||
} | ||
}; | ||
return rpc; | ||
} | ||
// Builds the transaction namespace. | ||
static buildTx(idlIx, ixFn) { | ||
const txFn = (...args) => { | ||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
const tx = new web3_js_1.Transaction(); | ||
if (ctx.instructions !== undefined) { | ||
tx.add(...ctx.instructions); | ||
} | ||
tx.add(ixFn(...args)); | ||
return tx; | ||
}; | ||
return txFn; | ||
} | ||
// Returns the generated accounts namespace. | ||
static buildAccounts(idl, coder, programId, provider) { | ||
const accountFns = {}; | ||
idl.accounts.forEach((idlAccount) => { | ||
const name = camelcase_1.default(idlAccount.name); | ||
// Fetches the decoded account from the network. | ||
const accountsNamespace = async (address) => { | ||
const accountInfo = await provider.connection.getAccountInfo(address); | ||
if (accountInfo === null) { | ||
throw new Error(`Account does not exist ${address.toString()}`); | ||
} | ||
// Assert the account discriminator is correct. | ||
const discriminator = await coder_1.accountDiscriminator(idlAccount.name); | ||
if (discriminator.compare(accountInfo.data.slice(0, 8))) { | ||
throw new Error("Invalid account discriminator"); | ||
} | ||
return coder.accounts.decode(idlAccount.name, accountInfo.data); | ||
}; | ||
// Returns the size of the account. | ||
// @ts-ignore | ||
accountsNamespace["size"] = | ||
coder_1.ACCOUNT_DISCRIMINATOR_SIZE + coder_1.accountSize(idl, idlAccount); | ||
// Returns an instruction for creating this account. | ||
// @ts-ignore | ||
accountsNamespace["createInstruction"] = async (account, sizeOverride) => { | ||
// @ts-ignore | ||
const size = accountsNamespace["size"]; | ||
return web3_js_1.SystemProgram.createAccount({ | ||
fromPubkey: provider.wallet.publicKey, | ||
newAccountPubkey: account.publicKey, | ||
space: sizeOverride !== null && sizeOverride !== void 0 ? sizeOverride : size, | ||
lamports: await provider.connection.getMinimumBalanceForRentExemption(sizeOverride !== null && sizeOverride !== void 0 ? sizeOverride : size), | ||
programId, | ||
}); | ||
}; | ||
// Subscribes to all changes to this account. | ||
// @ts-ignore | ||
accountsNamespace["subscribe"] = (address, commitment) => { | ||
if (subscriptions.get(address.toString())) { | ||
return subscriptions.get(address.toString()).ee; | ||
} | ||
const ee = new eventemitter3_1.default(); | ||
const listener = provider.connection.onAccountChange(address, (acc) => { | ||
const account = coder.accounts.decode(idlAccount.name, acc.data); | ||
ee.emit("change", account); | ||
}, commitment); | ||
subscriptions.set(address.toString(), { | ||
ee, | ||
listener, | ||
}); | ||
return ee; | ||
}; | ||
// Unsubscribes to account changes. | ||
// @ts-ignore | ||
accountsNamespace["unsubscribe"] = (address) => { | ||
let sub = subscriptions.get(address.toString()); | ||
if (!sub) { | ||
console.warn("Address is not subscribed"); | ||
return; | ||
} | ||
if (subscriptions) { | ||
provider.connection | ||
.removeAccountChangeListener(sub.listener) | ||
.then(() => { | ||
subscriptions.delete(address.toString()); | ||
}) | ||
.catch(console.error); | ||
} | ||
}; | ||
// Returns all instances of this account type for the program. | ||
// @ts-ignore | ||
accountsNamespace["all"] = async (filter) => { | ||
let bytes = await coder_1.accountDiscriminator(idlAccount.name); | ||
if (filter !== undefined) { | ||
bytes = Buffer.concat([bytes, filter]); | ||
} | ||
// @ts-ignore | ||
let resp = await provider.connection._rpcRequest("getProgramAccounts", [ | ||
programId.toBase58(), | ||
{ | ||
commitment: provider.connection.commitment, | ||
filters: [ | ||
{ | ||
memcmp: { | ||
offset: 0, | ||
bytes: bs58.encode(bytes), | ||
}, | ||
}, | ||
], | ||
}, | ||
]); | ||
if (resp.error) { | ||
console.error(resp); | ||
throw new Error("Failed to get accounts"); | ||
} | ||
return (resp.result | ||
// @ts-ignore | ||
.map(({ pubkey, account: { data } }) => { | ||
data = bs58.decode(data); | ||
return { | ||
publicKey: new web3_js_1.PublicKey(pubkey), | ||
account: coder.accounts.decode(idlAccount.name, data), | ||
}; | ||
})); | ||
}; | ||
// Function returning the associated address. Args are keys to associate. | ||
// Order matters. | ||
accountsNamespace["associatedAddress"] = async (...args) => { | ||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor". | ||
args.forEach((arg) => { | ||
seeds.push(arg.toBuffer()); | ||
}); | ||
const [assoc] = await web3_js_1.PublicKey.findProgramAddress(seeds, programId); | ||
return assoc; | ||
}; | ||
// Function returning the associated account. Args are keys to associate. | ||
// Order matters. | ||
accountsNamespace["associated"] = async (...args) => { | ||
const addr = await accountsNamespace["associatedAddress"](...args); | ||
return await accountsNamespace(addr); | ||
}; | ||
accountFns[name] = accountsNamespace; | ||
}); | ||
return accountFns; | ||
} | ||
} | ||
exports.RpcFactory = RpcFactory; | ||
function translateError(idlErrors, err) { | ||
@@ -389,103 +25,3 @@ // TODO: don't rely on the error string. web3.js should preserve the error | ||
} | ||
function parseIdlErrors(idl) { | ||
const errors = new Map(); | ||
if (idl.errors) { | ||
idl.errors.forEach((e) => { | ||
var _a; | ||
let msg = (_a = e.msg) !== null && _a !== void 0 ? _a : e.name; | ||
errors.set(e.code, msg); | ||
}); | ||
} | ||
return errors; | ||
} | ||
function splitArgsAndCtx(idlIx, args) { | ||
let options = {}; | ||
const inputLen = idlIx.args ? idlIx.args.length : 0; | ||
if (args.length > inputLen) { | ||
if (args.length !== inputLen + 1) { | ||
throw new Error("provided too many arguments ${args}"); | ||
} | ||
options = args.pop(); | ||
} | ||
return [args, options]; | ||
} | ||
// Allow either IdLInstruction or IdlStateMethod since the types share fields. | ||
function toInstruction(idlIx, ...args) { | ||
if (idlIx.args.length != args.length) { | ||
throw new Error("Invalid argument length"); | ||
} | ||
const ix = {}; | ||
let idx = 0; | ||
idlIx.args.forEach((ixArg) => { | ||
ix[ixArg.name] = args[idx]; | ||
idx += 1; | ||
}); | ||
return ix; | ||
} | ||
// Throws error if any account required for the `ix` is not given. | ||
function validateAccounts(ixAccounts, accounts) { | ||
ixAccounts.forEach((acc) => { | ||
// @ts-ignore | ||
if (acc.accounts !== undefined) { | ||
// @ts-ignore | ||
validateAccounts(acc.accounts, accounts[acc.name]); | ||
} | ||
else { | ||
if (accounts[acc.name] === undefined) { | ||
throw new Error(`Invalid arguments: ${acc.name} not provided.`); | ||
} | ||
} | ||
}); | ||
} | ||
// Throws error if any argument required for the `ix` is not given. | ||
function validateInstruction(ix, ...args) { | ||
// todo | ||
} | ||
// Calculates the deterministic address of the program's "state" account. | ||
async function programStateAddress(programId) { | ||
let [registrySigner, _nonce] = await web3_js_1.PublicKey.findProgramAddress([], programId); | ||
return web3_js_1.PublicKey.createWithSeed(registrySigner, "unversioned", programId); | ||
} | ||
// Returns the common keys that are prepended to all instructions targeting | ||
// the "state" of a program. | ||
async function stateInstructionKeys(programId, provider, m, accounts) { | ||
if (m.name === "new") { | ||
// Ctor `new` method. | ||
const [programSigner, _nonce] = await web3_js_1.PublicKey.findProgramAddress([], programId); | ||
return [ | ||
{ | ||
pubkey: provider.wallet.publicKey, | ||
isWritable: false, | ||
isSigner: true, | ||
}, | ||
{ | ||
pubkey: await programStateAddress(programId), | ||
isWritable: true, | ||
isSigner: false, | ||
}, | ||
{ pubkey: programSigner, isWritable: false, isSigner: false }, | ||
{ | ||
pubkey: web3_js_1.SystemProgram.programId, | ||
isWritable: false, | ||
isSigner: false, | ||
}, | ||
{ pubkey: programId, isWritable: false, isSigner: false }, | ||
{ | ||
pubkey: web3_js_1.SYSVAR_RENT_PUBKEY, | ||
isWritable: false, | ||
isSigner: false, | ||
}, | ||
]; | ||
} | ||
else { | ||
validateAccounts(m.accounts, accounts); | ||
return [ | ||
{ | ||
pubkey: await programStateAddress(programId), | ||
isWritable: true, | ||
isSigner: false, | ||
}, | ||
]; | ||
} | ||
} | ||
exports.translateError = translateError; | ||
//# sourceMappingURL=rpc.js.map |
@@ -25,3 +25,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.TOKEN_PROGRAM_ID = void 0; | ||
exports.decodeUtf8 = exports.TOKEN_PROGRAM_ID = void 0; | ||
const bs58 = __importStar(require("bs58")); | ||
@@ -76,2 +76,9 @@ const crypto_hash_1 = require("crypto-hash"); | ||
} | ||
function decodeUtf8(array) { | ||
const decoder = typeof TextDecoder === "undefined" | ||
? new (require("util").TextDecoder)("utf-8") // Node. | ||
: new TextDecoder("utf-8"); // Browser. | ||
return decoder.decode(array); | ||
} | ||
exports.decodeUtf8 = decodeUtf8; | ||
const utils = { | ||
@@ -78,0 +85,0 @@ bs58, |
import BN from "bn.js"; | ||
import * as web3 from "@solana/web3.js"; | ||
import Provider, { NodeWallet as Wallet } from "./provider"; | ||
import { Program } from "./program"; | ||
import Coder from "./coder"; | ||
import workspace from "./workspace"; | ||
import utils from "./utils"; | ||
import { Program } from "./program"; | ||
let _provider = null; | ||
@@ -9,0 +9,0 @@ function setProvider(provider) { |
import { inflate } from "pako"; | ||
import { RpcFactory } from "./rpc"; | ||
import { idlAddress, decodeIdlAccount } from "./idl"; | ||
import Coder, { eventDiscriminator } from "./coder"; | ||
import NamespaceFactory from "./namespace"; | ||
import { getProvider } from "./"; | ||
@@ -19,3 +19,3 @@ import * as base64 from "base64-js"; | ||
// Build the dynamic RPC functions. | ||
const [rpcs, ixs, txs, accounts, state] = RpcFactory.build(idl, coder, programId, this.provider); | ||
const [rpcs, ixs, txs, accounts, state] = NamespaceFactory.build(idl, coder, programId, this.provider); | ||
this.rpc = rpcs; | ||
@@ -22,0 +22,0 @@ this.instruction = ixs; |
@@ -1,344 +0,3 @@ | ||
import camelCase from "camelcase"; | ||
import EventEmitter from "eventemitter3"; | ||
import * as bs58 from "bs58"; | ||
import { PublicKey, SystemProgram, Transaction, TransactionInstruction, SYSVAR_RENT_PUBKEY, } from "@solana/web3.js"; | ||
import { IdlError, ProgramError } from "./error"; | ||
import { ACCOUNT_DISCRIMINATOR_SIZE, accountDiscriminator, stateDiscriminator, accountSize, } from "./coder"; | ||
// Tracks all subscriptions. | ||
const subscriptions = new Map(); | ||
/** | ||
* RpcFactory builds an Rpcs object for a given IDL. | ||
*/ | ||
export class RpcFactory { | ||
/** | ||
* build dynamically generates RPC methods. | ||
* | ||
* @returns an object with all the RPC methods attached. | ||
*/ | ||
static build(idl, coder, programId, provider) { | ||
const idlErrors = parseIdlErrors(idl); | ||
const rpcs = {}; | ||
const ixFns = {}; | ||
const txFns = {}; | ||
const state = RpcFactory.buildState(idl, coder, programId, idlErrors, provider); | ||
idl.instructions.forEach((idlIx) => { | ||
const name = camelCase(idlIx.name); | ||
// Function to create a raw `TransactionInstruction`. | ||
const ix = RpcFactory.buildIx(idlIx, coder, programId); | ||
// Ffnction to create a `Transaction`. | ||
const tx = RpcFactory.buildTx(idlIx, ix); | ||
// Function to invoke an RPC against a cluster. | ||
const rpc = RpcFactory.buildRpc(idlIx, tx, idlErrors, provider); | ||
rpcs[name] = rpc; | ||
ixFns[name] = ix; | ||
txFns[name] = tx; | ||
}); | ||
const accountFns = idl.accounts | ||
? RpcFactory.buildAccounts(idl, coder, programId, provider) | ||
: {}; | ||
return [rpcs, ixFns, txFns, accountFns, state]; | ||
} | ||
// Builds the state namespace. | ||
static buildState(idl, coder, programId, idlErrors, provider) { | ||
if (idl.state === undefined) { | ||
return undefined; | ||
} | ||
// Fetches the state object from the blockchain. | ||
const state = async () => { | ||
const addr = await programStateAddress(programId); | ||
const accountInfo = await provider.connection.getAccountInfo(addr); | ||
if (accountInfo === null) { | ||
throw new Error(`Account does not exist ${addr.toString()}`); | ||
} | ||
// Assert the account discriminator is correct. | ||
const expectedDiscriminator = await stateDiscriminator(idl.state.struct.name); | ||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) { | ||
throw new Error("Invalid account discriminator"); | ||
} | ||
return coder.state.decode(accountInfo.data); | ||
}; | ||
// Namespace with all rpc functions. | ||
const rpc = {}; | ||
const ix = {}; | ||
idl.state.methods.forEach((m) => { | ||
const accounts = async (accounts) => { | ||
const keys = await stateInstructionKeys(programId, provider, m, accounts); | ||
return keys.concat(RpcFactory.accountsArray(accounts, m.accounts)); | ||
}; | ||
const ixFn = async (...args) => { | ||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]); | ||
return new TransactionInstruction({ | ||
keys: await accounts(ctx.accounts), | ||
programId, | ||
data: coder.instruction.encodeState(m.name, toInstruction(m, ...ixArgs)), | ||
}); | ||
}; | ||
ixFn["accounts"] = accounts; | ||
ix[m.name] = ixFn; | ||
rpc[m.name] = async (...args) => { | ||
const [_, ctx] = splitArgsAndCtx(m, [...args]); | ||
const tx = new Transaction(); | ||
if (ctx.instructions !== undefined) { | ||
tx.add(...ctx.instructions); | ||
} | ||
tx.add(await ix[m.name](...args)); | ||
try { | ||
const txSig = await provider.send(tx, ctx.signers, ctx.options); | ||
return txSig; | ||
} | ||
catch (err) { | ||
let translatedErr = translateError(idlErrors, err); | ||
if (translatedErr === null) { | ||
throw err; | ||
} | ||
throw translatedErr; | ||
} | ||
}; | ||
}); | ||
state["rpc"] = rpc; | ||
state["instruction"] = ix; | ||
// Calculates the address of the program's global state object account. | ||
state["address"] = async () => programStateAddress(programId); | ||
// Subscription singleton. | ||
let sub = null; | ||
// Subscribe to account changes. | ||
state["subscribe"] = (commitment) => { | ||
if (sub !== null) { | ||
return sub.ee; | ||
} | ||
const ee = new EventEmitter(); | ||
state["address"]().then((address) => { | ||
const listener = provider.connection.onAccountChange(address, (acc) => { | ||
const account = coder.state.decode(acc.data); | ||
ee.emit("change", account); | ||
}, commitment); | ||
sub = { | ||
ee, | ||
listener, | ||
}; | ||
}); | ||
return ee; | ||
}; | ||
// Unsubscribe from account changes. | ||
state["unsubscribe"] = () => { | ||
if (sub !== null) { | ||
provider.connection | ||
.removeAccountChangeListener(sub.listener) | ||
.then(async () => { | ||
sub = null; | ||
}) | ||
.catch(console.error); | ||
} | ||
}; | ||
return state; | ||
} | ||
// Builds the instuction namespace. | ||
static buildIx(idlIx, coder, programId) { | ||
if (idlIx.name === "_inner") { | ||
throw new IdlError("the _inner name is reserved"); | ||
} | ||
const ix = (...args) => { | ||
const [ixArgs, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
validateAccounts(idlIx.accounts, ctx.accounts); | ||
validateInstruction(idlIx, ...args); | ||
const keys = RpcFactory.accountsArray(ctx.accounts, idlIx.accounts); | ||
if (ctx.remainingAccounts !== undefined) { | ||
keys.push(...ctx.remainingAccounts); | ||
} | ||
if (ctx.__private && ctx.__private.logAccounts) { | ||
console.log("Outgoing account metas:", keys); | ||
} | ||
return new TransactionInstruction({ | ||
keys, | ||
programId, | ||
data: coder.instruction.encode(idlIx.name, toInstruction(idlIx, ...ixArgs)), | ||
}); | ||
}; | ||
// Utility fn for ordering the accounts for this instruction. | ||
ix["accounts"] = (accs) => { | ||
return RpcFactory.accountsArray(accs, idlIx.accounts); | ||
}; | ||
return ix; | ||
} | ||
static accountsArray(ctx, accounts) { | ||
return accounts | ||
.map((acc) => { | ||
// Nested accounts. | ||
// @ts-ignore | ||
const nestedAccounts = acc.accounts; | ||
if (nestedAccounts !== undefined) { | ||
const rpcAccs = ctx[acc.name]; | ||
return RpcFactory.accountsArray(rpcAccs, nestedAccounts).flat(); | ||
} | ||
else { | ||
const account = acc; | ||
return { | ||
pubkey: ctx[acc.name], | ||
isWritable: account.isMut, | ||
isSigner: account.isSigner, | ||
}; | ||
} | ||
}) | ||
.flat(); | ||
} | ||
// Builds the rpc namespace. | ||
static buildRpc(idlIx, txFn, idlErrors, provider) { | ||
const rpc = async (...args) => { | ||
const tx = txFn(...args); | ||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
try { | ||
const txSig = await provider.send(tx, ctx.signers, ctx.options); | ||
return txSig; | ||
} | ||
catch (err) { | ||
console.log("Translating error", err); | ||
let translatedErr = translateError(idlErrors, err); | ||
if (translatedErr === null) { | ||
throw err; | ||
} | ||
throw translatedErr; | ||
} | ||
}; | ||
return rpc; | ||
} | ||
// Builds the transaction namespace. | ||
static buildTx(idlIx, ixFn) { | ||
const txFn = (...args) => { | ||
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]); | ||
const tx = new Transaction(); | ||
if (ctx.instructions !== undefined) { | ||
tx.add(...ctx.instructions); | ||
} | ||
tx.add(ixFn(...args)); | ||
return tx; | ||
}; | ||
return txFn; | ||
} | ||
// Returns the generated accounts namespace. | ||
static buildAccounts(idl, coder, programId, provider) { | ||
const accountFns = {}; | ||
idl.accounts.forEach((idlAccount) => { | ||
const name = camelCase(idlAccount.name); | ||
// Fetches the decoded account from the network. | ||
const accountsNamespace = async (address) => { | ||
const accountInfo = await provider.connection.getAccountInfo(address); | ||
if (accountInfo === null) { | ||
throw new Error(`Account does not exist ${address.toString()}`); | ||
} | ||
// Assert the account discriminator is correct. | ||
const discriminator = await accountDiscriminator(idlAccount.name); | ||
if (discriminator.compare(accountInfo.data.slice(0, 8))) { | ||
throw new Error("Invalid account discriminator"); | ||
} | ||
return coder.accounts.decode(idlAccount.name, accountInfo.data); | ||
}; | ||
// Returns the size of the account. | ||
// @ts-ignore | ||
accountsNamespace["size"] = | ||
ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount); | ||
// Returns an instruction for creating this account. | ||
// @ts-ignore | ||
accountsNamespace["createInstruction"] = async (account, sizeOverride) => { | ||
// @ts-ignore | ||
const size = accountsNamespace["size"]; | ||
return SystemProgram.createAccount({ | ||
fromPubkey: provider.wallet.publicKey, | ||
newAccountPubkey: account.publicKey, | ||
space: sizeOverride !== null && sizeOverride !== void 0 ? sizeOverride : size, | ||
lamports: await provider.connection.getMinimumBalanceForRentExemption(sizeOverride !== null && sizeOverride !== void 0 ? sizeOverride : size), | ||
programId, | ||
}); | ||
}; | ||
// Subscribes to all changes to this account. | ||
// @ts-ignore | ||
accountsNamespace["subscribe"] = (address, commitment) => { | ||
if (subscriptions.get(address.toString())) { | ||
return subscriptions.get(address.toString()).ee; | ||
} | ||
const ee = new EventEmitter(); | ||
const listener = provider.connection.onAccountChange(address, (acc) => { | ||
const account = coder.accounts.decode(idlAccount.name, acc.data); | ||
ee.emit("change", account); | ||
}, commitment); | ||
subscriptions.set(address.toString(), { | ||
ee, | ||
listener, | ||
}); | ||
return ee; | ||
}; | ||
// Unsubscribes to account changes. | ||
// @ts-ignore | ||
accountsNamespace["unsubscribe"] = (address) => { | ||
let sub = subscriptions.get(address.toString()); | ||
if (!sub) { | ||
console.warn("Address is not subscribed"); | ||
return; | ||
} | ||
if (subscriptions) { | ||
provider.connection | ||
.removeAccountChangeListener(sub.listener) | ||
.then(() => { | ||
subscriptions.delete(address.toString()); | ||
}) | ||
.catch(console.error); | ||
} | ||
}; | ||
// Returns all instances of this account type for the program. | ||
// @ts-ignore | ||
accountsNamespace["all"] = async (filter) => { | ||
let bytes = await accountDiscriminator(idlAccount.name); | ||
if (filter !== undefined) { | ||
bytes = Buffer.concat([bytes, filter]); | ||
} | ||
// @ts-ignore | ||
let resp = await provider.connection._rpcRequest("getProgramAccounts", [ | ||
programId.toBase58(), | ||
{ | ||
commitment: provider.connection.commitment, | ||
filters: [ | ||
{ | ||
memcmp: { | ||
offset: 0, | ||
bytes: bs58.encode(bytes), | ||
}, | ||
}, | ||
], | ||
}, | ||
]); | ||
if (resp.error) { | ||
console.error(resp); | ||
throw new Error("Failed to get accounts"); | ||
} | ||
return (resp.result | ||
// @ts-ignore | ||
.map(({ pubkey, account: { data } }) => { | ||
data = bs58.decode(data); | ||
return { | ||
publicKey: new PublicKey(pubkey), | ||
account: coder.accounts.decode(idlAccount.name, data), | ||
}; | ||
})); | ||
}; | ||
// Function returning the associated address. Args are keys to associate. | ||
// Order matters. | ||
accountsNamespace["associatedAddress"] = async (...args) => { | ||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor". | ||
args.forEach((arg) => { | ||
seeds.push(arg.toBuffer()); | ||
}); | ||
const [assoc] = await PublicKey.findProgramAddress(seeds, programId); | ||
return assoc; | ||
}; | ||
// Function returning the associated account. Args are keys to associate. | ||
// Order matters. | ||
accountsNamespace["associated"] = async (...args) => { | ||
const addr = await accountsNamespace["associatedAddress"](...args); | ||
return await accountsNamespace(addr); | ||
}; | ||
accountFns[name] = accountsNamespace; | ||
}); | ||
return accountFns; | ||
} | ||
} | ||
function translateError(idlErrors, err) { | ||
import { ProgramError } from "./error"; | ||
export function translateError(idlErrors, err) { | ||
// TODO: don't rely on the error string. web3.js should preserve the error | ||
@@ -363,103 +22,2 @@ // code information instead of giving us an untyped string. | ||
} | ||
function parseIdlErrors(idl) { | ||
const errors = new Map(); | ||
if (idl.errors) { | ||
idl.errors.forEach((e) => { | ||
var _a; | ||
let msg = (_a = e.msg) !== null && _a !== void 0 ? _a : e.name; | ||
errors.set(e.code, msg); | ||
}); | ||
} | ||
return errors; | ||
} | ||
function splitArgsAndCtx(idlIx, args) { | ||
let options = {}; | ||
const inputLen = idlIx.args ? idlIx.args.length : 0; | ||
if (args.length > inputLen) { | ||
if (args.length !== inputLen + 1) { | ||
throw new Error("provided too many arguments ${args}"); | ||
} | ||
options = args.pop(); | ||
} | ||
return [args, options]; | ||
} | ||
// Allow either IdLInstruction or IdlStateMethod since the types share fields. | ||
function toInstruction(idlIx, ...args) { | ||
if (idlIx.args.length != args.length) { | ||
throw new Error("Invalid argument length"); | ||
} | ||
const ix = {}; | ||
let idx = 0; | ||
idlIx.args.forEach((ixArg) => { | ||
ix[ixArg.name] = args[idx]; | ||
idx += 1; | ||
}); | ||
return ix; | ||
} | ||
// Throws error if any account required for the `ix` is not given. | ||
function validateAccounts(ixAccounts, accounts) { | ||
ixAccounts.forEach((acc) => { | ||
// @ts-ignore | ||
if (acc.accounts !== undefined) { | ||
// @ts-ignore | ||
validateAccounts(acc.accounts, accounts[acc.name]); | ||
} | ||
else { | ||
if (accounts[acc.name] === undefined) { | ||
throw new Error(`Invalid arguments: ${acc.name} not provided.`); | ||
} | ||
} | ||
}); | ||
} | ||
// Throws error if any argument required for the `ix` is not given. | ||
function validateInstruction(ix, ...args) { | ||
// todo | ||
} | ||
// Calculates the deterministic address of the program's "state" account. | ||
async function programStateAddress(programId) { | ||
let [registrySigner, _nonce] = await PublicKey.findProgramAddress([], programId); | ||
return PublicKey.createWithSeed(registrySigner, "unversioned", programId); | ||
} | ||
// Returns the common keys that are prepended to all instructions targeting | ||
// the "state" of a program. | ||
async function stateInstructionKeys(programId, provider, m, accounts) { | ||
if (m.name === "new") { | ||
// Ctor `new` method. | ||
const [programSigner, _nonce] = await PublicKey.findProgramAddress([], programId); | ||
return [ | ||
{ | ||
pubkey: provider.wallet.publicKey, | ||
isWritable: false, | ||
isSigner: true, | ||
}, | ||
{ | ||
pubkey: await programStateAddress(programId), | ||
isWritable: true, | ||
isSigner: false, | ||
}, | ||
{ pubkey: programSigner, isWritable: false, isSigner: false }, | ||
{ | ||
pubkey: SystemProgram.programId, | ||
isWritable: false, | ||
isSigner: false, | ||
}, | ||
{ pubkey: programId, isWritable: false, isSigner: false }, | ||
{ | ||
pubkey: SYSVAR_RENT_PUBKEY, | ||
isWritable: false, | ||
isSigner: false, | ||
}, | ||
]; | ||
} | ||
else { | ||
validateAccounts(m.accounts, accounts); | ||
return [ | ||
{ | ||
pubkey: await programStateAddress(programId), | ||
isWritable: true, | ||
isSigner: false, | ||
}, | ||
]; | ||
} | ||
} | ||
//# sourceMappingURL=rpc.js.map |
@@ -50,2 +50,8 @@ import * as bs58 from "bs58"; | ||
} | ||
export function decodeUtf8(array) { | ||
const decoder = typeof TextDecoder === "undefined" | ||
? new (require("util").TextDecoder)("utf-8") // Node. | ||
: new TextDecoder("utf-8"); // Browser. | ||
return decoder.decode(array); | ||
} | ||
const utils = { | ||
@@ -52,0 +58,0 @@ bs58, |
import BN from "bn.js"; | ||
import * as web3 from "@solana/web3.js"; | ||
import Provider, { NodeWallet as Wallet } from "./provider"; | ||
import { Program } from "./program"; | ||
import Coder from "./coder"; | ||
@@ -9,3 +8,4 @@ import { Idl } from "./idl"; | ||
import utils from "./utils"; | ||
import { ProgramAccount } from "./rpc"; | ||
import { Program } from "./program"; | ||
import { ProgramAccount } from "./program/namespace"; | ||
declare function setProvider(provider: Provider): void; | ||
@@ -12,0 +12,0 @@ declare function getProvider(): Provider; |
@@ -5,3 +5,3 @@ import { PublicKey } from "@solana/web3.js"; | ||
import Coder from "./coder"; | ||
import { Rpcs, Ixs, Txs, Accounts, State } from "./rpc"; | ||
import { Rpcs, Ixs, Txs, Accounts, State } from "./namespace"; | ||
/** | ||
@@ -8,0 +8,0 @@ * Program is the IDL deserialized representation of a Solana program. |
@@ -1,106 +0,2 @@ | ||
/// <reference types="node" /> | ||
import EventEmitter from "eventemitter3"; | ||
import { Account, PublicKey, ConfirmOptions, Transaction, TransactionSignature, TransactionInstruction, Commitment } from "@solana/web3.js"; | ||
import Provider from "./provider"; | ||
import { Idl } from "./idl"; | ||
import Coder from "./coder"; | ||
/** | ||
* Dynamically generated rpc namespace. | ||
*/ | ||
export interface Rpcs { | ||
[key: string]: RpcFn; | ||
} | ||
/** | ||
* Dynamically generated instruction namespace. | ||
*/ | ||
export interface Ixs { | ||
[key: string]: IxFn; | ||
} | ||
/** | ||
* Dynamically generated transaction namespace. | ||
*/ | ||
export interface Txs { | ||
[key: string]: TxFn; | ||
} | ||
/** | ||
* Accounts is a dynamically generated object to fetch any given account | ||
* of a program. | ||
*/ | ||
export interface Accounts { | ||
[key: string]: AccountFn; | ||
} | ||
/** | ||
* RpcFn is a single rpc method generated from an IDL. | ||
*/ | ||
export declare type RpcFn = (...args: any[]) => Promise<TransactionSignature>; | ||
/** | ||
* Ix is a function to create a `TransactionInstruction` generated from an IDL. | ||
*/ | ||
export declare type IxFn = IxProps & ((...args: any[]) => any); | ||
declare type IxProps = { | ||
accounts: (ctx: RpcAccounts) => any; | ||
}; | ||
/** | ||
* Tx is a function to create a `Transaction` generate from an IDL. | ||
*/ | ||
export declare type TxFn = (...args: any[]) => Transaction; | ||
/** | ||
* Account is a function returning a deserialized account, given an address. | ||
*/ | ||
export declare type AccountFn<T = any> = AccountProps & ((address: PublicKey) => T); | ||
/** | ||
* Deserialized account owned by a program. | ||
*/ | ||
export declare type ProgramAccount<T = any> = { | ||
publicKey: PublicKey; | ||
account: T; | ||
}; | ||
/** | ||
* Non function properties on the acccount namespace. | ||
*/ | ||
declare type AccountProps = { | ||
size: number; | ||
all: (filter?: Buffer) => Promise<ProgramAccount<any>[]>; | ||
subscribe: (address: PublicKey, commitment?: Commitment) => EventEmitter; | ||
unsubscribe: (address: PublicKey) => void; | ||
createInstruction: (account: Account) => Promise<TransactionInstruction>; | ||
associated: (...args: PublicKey[]) => Promise<any>; | ||
associatedAddress: (...args: PublicKey[]) => Promise<PublicKey>; | ||
}; | ||
/** | ||
* Options for an RPC invocation. | ||
*/ | ||
export declare type RpcOptions = ConfirmOptions; | ||
/** | ||
* Dynamic object representing a set of accounts given to an rpc/ix invocation. | ||
* The name of each key should match the name for that account in the IDL. | ||
*/ | ||
declare type RpcAccounts = { | ||
[key: string]: PublicKey | RpcAccounts; | ||
}; | ||
export declare type State = () => Promise<any> | { | ||
address: () => Promise<PublicKey>; | ||
rpc: Rpcs; | ||
instruction: Ixs; | ||
subscribe: (address: PublicKey, commitment?: Commitment) => EventEmitter; | ||
unsubscribe: (address: PublicKey) => void; | ||
}; | ||
/** | ||
* RpcFactory builds an Rpcs object for a given IDL. | ||
*/ | ||
export declare class RpcFactory { | ||
/** | ||
* build dynamically generates RPC methods. | ||
* | ||
* @returns an object with all the RPC methods attached. | ||
*/ | ||
static build(idl: Idl, coder: Coder, programId: PublicKey, provider: Provider): [Rpcs, Ixs, Txs, Accounts, State]; | ||
private static buildState; | ||
private static buildIx; | ||
private static accountsArray; | ||
private static buildRpc; | ||
private static buildTx; | ||
private static buildAccounts; | ||
} | ||
export {}; | ||
export declare function translateError(idlErrors: Map<number, string>, err: any): Error | null; | ||
//# sourceMappingURL=rpc.d.ts.map |
@@ -10,2 +10,3 @@ /// <reference types="node" /> | ||
}>>; | ||
export declare function decodeUtf8(array: Uint8Array): string; | ||
declare const utils: { | ||
@@ -12,0 +13,0 @@ bs58: import("base-x").BaseConverter; |
{ | ||
"name": "@project-serum/anchor", | ||
"version": "0.5.0", | ||
"version": "0.5.1-beta.1", | ||
"description": "Anchor client", | ||
@@ -5,0 +5,0 @@ "main": "dist/cjs/index.js", |
import camelCase from "camelcase"; | ||
import { snakeCase } from "snake-case"; | ||
import { Layout, seq } from "buffer-layout"; | ||
import { Layout } from "buffer-layout"; | ||
import * as sha256 from "js-sha256"; | ||
@@ -5,0 +5,0 @@ import * as borsh from "@project-serum/borsh"; |
import BN from "bn.js"; | ||
import * as web3 from "@solana/web3.js"; | ||
import Provider, { NodeWallet as Wallet } from "./provider"; | ||
import { Program } from "./program"; | ||
import Coder from "./coder"; | ||
@@ -9,3 +8,4 @@ import { Idl } from "./idl"; | ||
import utils from "./utils"; | ||
import { ProgramAccount } from "./rpc"; | ||
import { Program } from "./program"; | ||
import { ProgramAccount } from "./program/namespace"; | ||
@@ -12,0 +12,0 @@ let _provider: Provider | null = null; |
@@ -72,2 +72,10 @@ import * as bs58 from "bs58"; | ||
export function decodeUtf8(array: Uint8Array): string { | ||
const decoder = | ||
typeof TextDecoder === "undefined" | ||
? new (require("util").TextDecoder)("utf-8") // Node. | ||
: new TextDecoder("utf-8"); // Browser. | ||
return decoder.decode(array); | ||
} | ||
const utils = { | ||
@@ -74,0 +82,0 @@ bs58, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
722060
228
8328
1