Changes for a functioning lockup/stake UI (#46)

This commit is contained in:
Armani Ferrante 2021-01-27 19:31:15 -08:00 committed by GitHub
parent b9e7123f40
commit 930aa1d9f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1131 additions and 410 deletions

View File

@ -263,12 +263,16 @@ fn test() -> Result<()> {
// Switch again (todo: restore cwd in `build` command).
set_workspace_dir_or_exit();
// Bootup validator.
let mut validator_handle = start_test_validator()?;
// Deploy all programs.
let cfg = Config::discover()?.expect("Inside a workspace").0;
let programs = deploy_ws("http://localhost:8899", &cfg.wallet.to_string())?;
// Bootup validator.
let validator_handle = match cfg.cluster.url() {
"http://127.0.0.1:8899" => Some(start_test_validator()?),
_ => None,
};
let programs = deploy_ws(cfg.cluster.url(), &cfg.wallet.to_string())?;
// Store deployed program addresses in IDL metadata (for consumption by
// client + tests).
@ -290,14 +294,19 @@ fn test() -> Result<()> {
.arg("-t")
.arg("10000")
.arg("tests/")
.env("ANCHOR_PROVIDER_URL", cfg.cluster.url())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
{
validator_handle.kill()?;
if let Some(mut validator_handle) = validator_handle {
validator_handle.kill()?;
}
return Err(anyhow::format_err!("{}", e.to_string()));
}
validator_handle.kill()?;
if let Some(mut validator_handle) = validator_handle {
validator_handle.kill()?;
}
Ok(())
}
@ -350,18 +359,59 @@ fn start_test_validator() -> Result<Child> {
Ok(validator_handle)
}
// TODO: Testing and deploys should use separate sections of metadata.
// Similarly, each network should have separate metadata.
fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
let (cfg, ws_path, _) = Config::discover()?.ok_or(anyhow!("Not in Anchor workspace."))?;
std::env::set_current_dir(ws_path.parent().unwrap())?;
// Build all programs.
set_workspace_dir_or_exit();
build(None)?;
set_workspace_dir_or_exit();
// Deploy all programs.
let cfg = Config::discover()?.expect("Inside a workspace").0;
let url = url.unwrap_or(cfg.cluster.url().to_string());
let keypair = keypair.unwrap_or(cfg.wallet.to_string());
let deployment = deploy_ws(&url, &keypair)?;
// Add metadata to all IDLs.
for (program, address) in deployment {
println!("Deployed {} at {}", program.idl.name, address.to_string());
// Add metadata to the IDL.
let mut idl = program.idl;
idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
address: address.to_string(),
})?);
// Persist it.
let idl_out = PathBuf::from("target/idl")
.join(&idl.name)
.with_extension("json");
write_idl(&idl, Some(&idl_out))?;
println!("Deployed {} at {}", idl.name, address.to_string());
}
run_hosted_deploy(&url, &keypair)?;
Ok(())
}
fn run_hosted_deploy(url: &str, keypair: &str) -> Result<()> {
println!("Running deploy script");
let cur_dir = std::env::current_dir()?;
let module_path = format!("{}/migrations/deploy.js", cur_dir.display());
let deploy_script_str = template::deploy_script(url, &module_path);
std::env::set_current_dir(".anchor")?;
std::fs::write("deploy.js", deploy_script_str)?;
if let Err(e) = std::process::Command::new("node")
.arg("deploy.js")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
{
std::process::exit(1);
}
Ok(())
}
@ -392,7 +442,7 @@ fn deploy_ws(url: &str, keypair: &str) -> Result<Vec<(Program, Pubkey)>> {
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "PascalCase")]
pub struct DeployStdout {
program_id: String,
}

View File

@ -34,6 +34,35 @@ anchor-lang = {{ git = "https://github.com/project-serum/anchor", features = ["d
)
}
pub fn deploy_script(cluster_url: &str, script_path: &str) -> String {
format!(
r#"
const serumCmn = require("@project-serum/common");
const anchor = require('@project-serum/anchor');
// Deploy script defined by the user.
const userScript = require("{0}");
async function main() {{
const url = "{1}";
const preflightCommitment = 'recent';
const connection = new anchor.web3.Connection(url, preflightCommitment);
const wallet = serumCmn.NodeWallet.local();
const provider = new serumCmn.Provider(connection, wallet, {{
preflightCommitment,
commitment: 'recent',
}});
// Run the user's deploy script.
userScript(provider);
}}
main();
"#,
script_path, cluster_url,
)
}
pub fn xargo_toml() -> String {
r#"[target.bpfel-unknown-unknown.dependencies.std]
features = []"#

View File

@ -0,0 +1,179 @@
// deploy.js is a simple deploy script to initialize a program. This is run
// immediately after a deploy.
const serumCmn = require("@project-serum/common");
const anchor = require("@project-serum/anchor");
const PublicKey = anchor.web3.PublicKey;
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Setup genesis state.
const registrarConfigs = await genesis(provider);
// Program clients.
const lockup = anchor.workspace.Lockup;
const registry = anchor.workspace.Registry;
// Registry state constructor.
await registry.state.rpc.new({
accounts: {
lockupProgram: lockup.programId,
},
});
// Lockup state constructor.
await lockup.state.rpc.new({
accounts: {
authority: provider.wallet.publicKey,
},
});
// Delete the default whitelist entries.
const defaultEntry = { programId: new anchor.web3.PublicKey() };
await lockup.state.rpc.whitelistDelete(defaultEntry, {
accounts: {
authority: provider.wallet.publicKey,
},
});
// Whitelist the registry.
await lockup.state.rpc.whitelistAdd(
{ programId: registry.programId },
{
accounts: {
authority: provider.wallet.publicKey,
},
}
);
// Initialize all registrars.
const cfgKeys = Object.keys(registrarConfigs);
for (let k = 0; k < cfgKeys.length; k += 1) {
let r = registrarConfigs[cfgKeys[k]];
const registrar = await registrarInit(
registry,
r.withdrawalTimelock,
r.stakeRate,
r.rewardQLen,
new anchor.web3.PublicKey(r.mint)
);
r["registrar"] = registrar.toString();
}
// Generate code for whitelisting on UIs.
const code = generateCode(registry, lockup, registrarConfigs);
console.log("Generated whitelisted UI addresses:", code);
};
function generateCode(registry, lockup, registrarConfigs) {
const registrars = Object.keys(registrarConfigs)
.map((cfg) => `${cfg}: new PublicKey('${registrarConfigs[cfg].registrar}')`)
.join(",");
const mints = Object.keys(registrarConfigs)
.map((cfg) => `${cfg}: new PublicKey('${registrarConfigs[cfg].mint}')`)
.join(",");
return `{
registryProgramId: new PublicKey('${registry.programId}'),
lockupProgramId: new PublicKey('${lockup.programId}'),
registrars: { ${registrars} },
mints: { ${mints} },
}`;
}
async function genesis(provider) {
if (
provider.connection._rpcEndpoint === "https://api.mainnet-beta.solana.com"
) {
return {
srm: {
withdrawalTimelock: 60,
stakeRate: 1000 * 10 ** 6,
rewardQLen: 100,
mint: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt",
},
msrm: {
withdrawalTimelock: 45,
stakeRate: 1,
rewardQLen: 100,
mint: "MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L",
},
};
} else {
const [token1Mint, _god1] = await serumCmn.createMintAndVault(
provider,
new anchor.BN(10000000000000),
undefined,
6
);
const [token2Mint, _god2] = await serumCmn.createMintAndVault(
provider,
new anchor.BN(10000000000),
undefined,
0
);
return {
token1: {
withdrawalTimelock: 60,
stakeRate: 2 * 10 ** 6,
rewardQLen: 100,
mint: token1Mint.toString(),
},
token2: {
withdrawalTimelock: 45,
stakeRate: 1,
rewardQLen: 100,
mint: token2Mint.toString(),
},
};
}
}
async function registrarInit(
registry,
_withdrawalTimelock,
_stakeRate,
rewardQLen,
mint
) {
const registrar = new anchor.web3.Account();
const rewardQ = new anchor.web3.Account();
const withdrawalTimelock = new anchor.BN(_withdrawalTimelock);
const stakeRate = new anchor.BN(_stakeRate);
const [
registrarSigner,
nonce,
] = await anchor.web3.PublicKey.findProgramAddress(
[registrar.publicKey.toBuffer()],
registry.programId
);
const poolMint = await serumCmn.createMint(
registry.provider,
registrarSigner
);
await registry.rpc.initialize(
mint,
registry.provider.wallet.publicKey,
nonce,
withdrawalTimelock,
stakeRate,
rewardQLen,
{
accounts: {
registrar: registrar.publicKey,
poolMint,
rewardEventQ: rewardQ.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [registrar, rewardQ],
instructions: [
await registry.account.registrar.createInstruction(registrar),
await registry.account.rewardQueue.createInstruction(rewardQ, 8250),
],
}
);
return registrar.publicKey;
}

View File

@ -131,8 +131,8 @@ pub mod lockup {
}
// Sends funds from the lockup program to a whitelisted program.
pub fn whitelist_withdraw(
ctx: Context<WhitelistWithdraw>,
pub fn whitelist_withdraw<'a, 'b, 'c, 'info>(
ctx: Context<'a, 'b, 'c, 'info, WhitelistWithdraw<'info>>,
instruction_data: Vec<u8>,
amount: u64,
) -> Result<()> {
@ -157,8 +157,8 @@ pub mod lockup {
}
// Sends funds from a whitelisted program back to the lockup program.
pub fn whitelist_deposit(
ctx: Context<WhitelistDeposit>,
pub fn whitelist_deposit<'a, 'b, 'c, 'info>(
ctx: Context<'a, 'b, 'c, 'info, WhitelistDeposit<'info>>,
instruction_data: Vec<u8>,
) -> Result<()> {
let before_amount = ctx.accounts.transfer.vault.amount;
@ -393,7 +393,7 @@ impl<'a, 'b, 'c, 'info> From<&Withdraw<'info>> for CpiContext<'a, 'b, 'c, 'info,
#[access_control(is_whitelisted(transfer))]
pub fn whitelist_relay_cpi<'info>(
transfer: &WhitelistTransfer,
transfer: &WhitelistTransfer<'info>,
remaining_accounts: &[AccountInfo<'info>],
instruction_data: Vec<u8>,
) -> Result<()> {
@ -432,7 +432,9 @@ pub fn whitelist_relay_cpi<'info>(
&[transfer.vesting.nonce],
];
let signer = &[&seeds[..]];
solana_program::program::invoke_signed(&relay_instruction, &transfer.to_account_infos(), signer)
let mut accounts = transfer.to_account_infos();
accounts.extend_from_slice(&remaining_accounts);
solana_program::program::invoke_signed(&relay_instruction, &accounts, signer)
.map_err(Into::into)
}

View File

@ -160,6 +160,10 @@ mod registry {
token::mint_to(cpi_ctx, spt_amount)?;
}
// Update stake timestamp.
let member = &mut ctx.accounts.member;
member.last_stake_ts = ctx.accounts.clock.unix_timestamp;
Ok(())
}
@ -231,6 +235,10 @@ mod registry {
pending_withdrawal.registrar = *ctx.accounts.registrar.to_account_info().key;
pending_withdrawal.locked = locked;
// Update stake timestamp.
let member = &mut ctx.accounts.member;
member.last_stake_ts = ctx.accounts.clock.unix_timestamp;
Ok(())
}
@ -349,12 +357,14 @@ mod registry {
let vendor = &mut ctx.accounts.vendor;
vendor.registrar = *ctx.accounts.registrar.to_account_info().key;
vendor.vault = *ctx.accounts.vendor_vault.to_account_info().key;
vendor.mint = ctx.accounts.vendor_vault.mint;
vendor.nonce = nonce;
vendor.pool_token_supply = ctx.accounts.pool_mint.supply;
vendor.reward_event_q_cursor = cursor;
vendor.start_ts = ctx.accounts.clock.unix_timestamp;
vendor.expiry_ts = expiry_ts;
vendor.expiry_receiver = expiry_receiver;
vendor.from = *ctx.accounts.depositor_authority.key;
vendor.total = total;
vendor.expired = false;
vendor.kind = kind.clone();
@ -497,6 +507,7 @@ pub struct Initialize<'info> {
registrar: ProgramAccount<'info, Registrar>,
#[account(init)]
reward_event_q: ProgramAccount<'info, RewardQueue>,
#[account("pool_mint.decimals == 0")]
pool_mint: CpiAccount<'info, Mint>,
rent: Sysvar<'info, Rent>,
}
@ -1096,12 +1107,14 @@ pub struct RewardEvent {
pub struct RewardVendor {
pub registrar: Pubkey,
pub vault: Pubkey,
pub mint: Pubkey,
pub nonce: u8,
pub pool_token_supply: u64,
pub reward_event_q_cursor: u32,
pub start_ts: i64,
pub expiry_ts: i64,
pub expiry_receiver: Pubkey,
pub from: Pubkey,
pub total: u64,
pub expired: bool,
pub kind: RewardVendorKind,

View File

@ -5,9 +5,10 @@ const TokenInstructions = require("@project-serum/serum").TokenInstructions;
const utils = require("./utils");
describe("Lockup and Registry", () => {
const provider = anchor.Provider.local();
// Read the provider from the configured environmnet.
const provider = anchor.Provider.env();
// Configure the client to use the local cluster.
// Configure the client to use the provider.
anchor.setProvider(provider);
const lockup = anchor.workspace.Lockup;

View File

@ -1,6 +1,6 @@
{
"name": "@project-serum/anchor",
"version": "0.0.0-alpha.7",
"version": "0.0.0-alpha.8",
"description": "Anchor client",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
@ -15,6 +15,7 @@
"scripts": {
"build": "yarn build:node",
"build:node": "tsc && tsc -p tsconfig.cjs.json",
"lint:fix": "prettier src/** -w",
"watch": "tsc -p tsconfig.cjs.json --watch",
"test:unit": "jest test/unit",
"test:integration": "jest test/integration --detectOpenHandles",
@ -23,14 +24,15 @@
},
"dependencies": {
"@project-serum/borsh": "^0.0.1-beta.0",
"@project-serum/common": "^0.0.1-beta.0",
"@project-serum/lockup": "^0.0.1-beta.0",
"@solana/spl-token": "0.0.11",
"@solana/web3.js": "^0.90.4",
"@types/bn.js": "^4.11.6",
"@types/bs58": "^4.0.1",
"bn.js": "^5.1.2",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.0",
"camelcase": "^5.3.1",
"crypto-hash": "^1.3.0",
"eventemitter3": "^4.0.7",
"find": "^0.3.0"
},
"devDependencies": {

View File

@ -1,5 +1,6 @@
import camelCase from "camelcase";
import { Layout } from "buffer-layout";
import { sha256 } from "crypto-hash";
import * as borsh from "@project-serum/borsh";
import {
Idl,
@ -11,6 +12,11 @@ import {
} from "./idl";
import { IdlError } from "./error";
/**
* Number of bytes of the account discriminator.
*/
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
/**
* Coder provides a facade for encoding and decoding all IDL related objects.
*/
@ -105,24 +111,30 @@ class AccountsCoder {
this.accountLayouts = new Map();
return;
}
const layouts = idl.accounts.map((acc) => {
const layouts: [string, Layout][] = idl.accounts.map((acc) => {
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
});
// @ts-ignore
this.accountLayouts = new Map(layouts);
}
public encode<T = any>(accountName: string, account: T): Buffer {
public async encode<T = any>(
accountName: string,
account: T
): Promise<Buffer> {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const layout = this.accountLayouts.get(accountName);
const len = layout.encode(account, buffer);
return buffer.slice(0, len);
let accountData = buffer.slice(0, len);
let discriminator = await accountDiscriminator(accountName);
return Buffer.concat([discriminator, accountData]);
}
public decode<T = any>(accountName: string, ix: Buffer): T {
// Chop off the discriminator before decoding.
const data = ix.slice(8);
const layout = this.accountLayouts.get(accountName);
return layout.decode(ix);
return layout.decode(data);
}
}
@ -171,14 +183,20 @@ class StateCoder {
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
}
public encode<T = any>(account: T): Buffer {
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const len = this.layout.encode(account, buffer);
return buffer.slice(0, len);
const disc = await stateDiscriminator(name);
const accData = buffer.slice(0, len);
return Buffer.concat([disc, accData]);
}
public decode<T = any>(ix: Buffer): T {
return this.layout.decode(ix);
// Chop off discriminator.
const data = ix.slice(8);
return this.layout.decode(data);
}
}
@ -299,3 +317,110 @@ class IdlCoder {
}
}
}
// Calculates unique 8 byte discriminator prepended to all anchor accounts.
export async function accountDiscriminator(name: string): Promise<Buffer> {
return Buffer.from(
(
await sha256(`account:${name}`, {
outputFormat: "buffer",
})
).slice(0, 8)
);
}
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
export async function stateDiscriminator(name: string): Promise<Buffer> {
return Buffer.from(
(
await sha256(`state:${name}`, {
outputFormat: "buffer",
})
).slice(0, 8)
);
}
// Returns the size of the type in bytes. For variable length types, just return
// 1. Users should override this value in such cases.
export function typeSize(idl: Idl, ty: IdlType): number {
switch (ty) {
case "bool":
return 1;
case "u8":
return 1;
case "i8":
return 1;
case "u16":
return 2;
case "u32":
return 4;
case "u64":
return 8;
case "i64":
return 8;
case "bytes":
return 1;
case "string":
return 1;
case "publicKey":
return 32;
default:
// @ts-ignore
if (ty.vec !== undefined) {
return 1;
}
// @ts-ignore
if (ty.option !== undefined) {
// @ts-ignore
return 1 + typeSize(ty.option);
}
// @ts-ignore
if (ty.defined !== undefined) {
// @ts-ignore
const filtered = idl.types.filter((t) => t.name === ty.defined);
if (filtered.length !== 1) {
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
}
let typeDef = filtered[0];
return accountSize(idl, typeDef);
}
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
}
}
export function accountSize(
idl: Idl,
idlAccount: IdlTypeDef
): number | undefined {
if (idlAccount.type.kind === "enum") {
let variantSizes = idlAccount.type.variants.map(
(variant: IdlEnumVariant) => {
if (variant.fields === undefined) {
return 0;
}
// @ts-ignore
return (
variant.fields
// @ts-ignore
.map((f: IdlField | IdlType) => {
// @ts-ignore
if (f.name === undefined) {
throw new Error("Tuple enum variants not yet implemented.");
}
// @ts-ignore
return typeSize(idl, f.type);
})
.reduce((a: number, b: number) => a + b)
);
}
);
return Math.max(...variantSizes) + 1;
}
if (idlAccount.type.fields === undefined) {
return 0;
}
return idlAccount.type.fields
.map((f) => typeSize(idl, f.type))
.reduce((a, b) => a + b);
}

View File

@ -1,9 +1,12 @@
import BN from "bn.js";
import * as web3 from "@solana/web3.js";
import { Provider } from "@project-serum/common";
import Provider, { NodeWallet as Wallet } from "./provider";
import { Program } from "./program";
import Coder from "./coder";
import { Idl } from "./idl";
import workspace from "./workspace";
import utils from "./utils";
import { ProgramAccount } from "./rpc";
let _provider: Provider | null = null;
@ -12,16 +15,23 @@ function setProvider(provider: Provider) {
}
function getProvider(): Provider {
if (_provider === null) {
return Provider.local();
}
return _provider;
}
export {
workspace,
Program,
ProgramAccount,
Coder,
setProvider,
getProvider,
Provider,
BN,
web3,
Idl,
utils,
Wallet,
};

View File

@ -1,8 +1,10 @@
import { PublicKey } from "@solana/web3.js";
import Provider from "./provider";
import { RpcFactory } from "./rpc";
import { Idl } from "./idl";
import Coder from "./coder";
import { Rpcs, Ixs, Txs, Accounts, State } from "./rpc";
import { getProvider } from "./";
/**
* Program is the IDL deserialized representation of a Solana program.
@ -49,9 +51,15 @@ export class Program {
*/
readonly state: State;
public constructor(idl: Idl, programId: PublicKey) {
/**
* Wallet and network provider.
*/
readonly provider: Provider;
public constructor(idl: Idl, programId: PublicKey, provider?: Provider) {
this.idl = idl;
this.programId = programId;
this.provider = provider ?? getProvider();
// Build the serializer.
const coder = new Coder(idl);
@ -60,7 +68,8 @@ export class Program {
const [rpcs, ixs, txs, accounts, state] = RpcFactory.build(
idl,
coder,
programId
programId,
this.provider
);
this.rpc = rpcs;
this.instruction = ixs;

184
ts/src/provider.ts Normal file
View File

@ -0,0 +1,184 @@
import {
Connection,
Account,
PublicKey,
Transaction,
TransactionSignature,
ConfirmOptions,
sendAndConfirmRawTransaction,
} from "@solana/web3.js";
export default class Provider {
constructor(
readonly connection: Connection,
readonly wallet: Wallet,
readonly opts: ConfirmOptions
) {}
static defaultOptions(): ConfirmOptions {
return {
preflightCommitment: "recent",
commitment: "recent",
};
}
// Node only api.
static local(url?: string, opts?: ConfirmOptions): Provider {
opts = opts || Provider.defaultOptions();
const connection = new Connection(
url || "http://localhost:8899",
opts.preflightCommitment
);
const wallet = NodeWallet.local();
return new Provider(connection, wallet, opts);
}
// Node only api.
static env(): Provider {
const process = require("process");
const url = process.env.ANCHOR_PROVIDER_URL;
if (url === undefined) {
throw new Error("ANCHOR_PROVIDER_URL is not defined");
}
const options = Provider.defaultOptions();
const connection = new Connection(url, options.commitment);
const wallet = NodeWallet.local();
return new Provider(connection, wallet, options);
}
async send(
tx: Transaction,
signers?: Array<Account | undefined>,
opts?: ConfirmOptions
): Promise<TransactionSignature> {
if (signers === undefined) {
signers = [];
}
if (opts === undefined) {
opts = this.opts;
}
const signerKps = signers.filter((s) => s !== undefined) as Array<Account>;
const signerPubkeys = [this.wallet.publicKey].concat(
signerKps.map((s) => s.publicKey)
);
tx.setSigners(...signerPubkeys);
tx.recentBlockhash = (
await this.connection.getRecentBlockhash(opts.preflightCommitment)
).blockhash;
await this.wallet.signTransaction(tx);
signerKps.forEach((kp) => {
tx.partialSign(kp);
});
const rawTx = tx.serialize();
const txId = await sendAndConfirmRawTransaction(
this.connection,
rawTx,
opts
);
return txId;
}
async sendAll(
reqs: Array<SendTxRequest>,
opts?: ConfirmOptions
): Promise<Array<TransactionSignature>> {
if (opts === undefined) {
opts = this.opts;
}
const blockhash = await this.connection.getRecentBlockhash(
opts.preflightCommitment
);
let txs = reqs.map((r) => {
let tx = r.tx;
let signers = r.signers;
if (signers === undefined) {
signers = [];
}
const signerKps = signers.filter(
(s) => s !== undefined
) as Array<Account>;
const signerPubkeys = [this.wallet.publicKey].concat(
signerKps.map((s) => s.publicKey)
);
tx.setSigners(...signerPubkeys);
tx.recentBlockhash = blockhash.blockhash;
signerKps.forEach((kp) => {
tx.partialSign(kp);
});
return tx;
});
const signedTxs = await this.wallet.signAllTransactions(txs);
const sigs = [];
for (let k = 0; k < txs.length; k += 1) {
const tx = signedTxs[k];
const rawTx = tx.serialize();
sigs.push(
await sendAndConfirmRawTransaction(this.connection, rawTx, opts)
);
}
return sigs;
}
}
export type SendTxRequest = {
tx: Transaction;
signers: Array<Account | undefined>;
};
export interface Wallet {
signTransaction(tx: Transaction): Promise<Transaction>;
signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
publicKey: PublicKey;
}
export class NodeWallet implements Wallet {
constructor(readonly payer: Account) {}
static local(): NodeWallet {
const payer = new Account(
Buffer.from(
JSON.parse(
require("fs").readFileSync(
require("os").homedir() + "/.config/solana/id.json",
{
encoding: "utf-8",
}
)
)
)
);
return new NodeWallet(payer);
}
async signTransaction(tx: Transaction): Promise<Transaction> {
tx.partialSign(this.payer);
return tx;
}
async signAllTransactions(txs: Transaction[]): Promise<Transaction[]> {
return txs.map((t) => {
t.partialSign(this.payer);
return t;
});
}
get publicKey(): PublicKey {
return this.payer.publicKey;
}
}

View File

@ -1,4 +1,6 @@
import camelCase from "camelcase";
import EventEmitter from "eventemitter3";
import * as bs58 from "bs58";
import {
Account,
AccountMeta,
@ -9,42 +11,41 @@ import {
TransactionSignature,
TransactionInstruction,
SYSVAR_RENT_PUBKEY,
Commitment,
} from "@solana/web3.js";
import { sha256 } from "crypto-hash";
import Provider from "./provider";
import {
Idl,
IdlAccount,
IdlInstruction,
IdlTypeDef,
IdlType,
IdlField,
IdlEnumVariant,
IdlAccountItem,
IdlStateMethod,
} from "./idl";
import { IdlError, ProgramError } from "./error";
import Coder from "./coder";
import { getProvider } from "./";
import Coder, {
ACCOUNT_DISCRIMINATOR_SIZE,
accountDiscriminator,
stateDiscriminator,
accountSize,
} from "./coder";
/**
* Number of bytes of the account discriminator.
*/
const ACCOUNT_DISCRIMINATOR_SIZE = 8;
/**
* Rpcs is a dynamically generated object with rpc methods attached.
* Dynamically generated rpc namespace.
*/
export interface Rpcs {
[key: string]: RpcFn;
}
/**
* Ixs is a dynamically generated object with ix functions attached.
* Dynamically generated instruction namespace.
*/
export interface Ixs {
[key: string]: IxFn;
}
/**
* Dynamically generated transaction namespace.
*/
export interface Txs {
[key: string]: TxFn;
}
@ -65,7 +66,11 @@ export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
/**
* Ix is a function to create a `TransactionInstruction` generated from an IDL.
*/
export type IxFn = (...args: any[]) => TransactionInstruction;
export type IxFn = IxProps & ((...args: any[]) => TransactionInstruction);
type IxProps = {
accounts: (ctx: RpcAccounts) => any;
};
/**
* Tx is a function to create a `Transaction` generate from an IDL.
@ -75,7 +80,26 @@ export type TxFn = (...args: any[]) => Transaction;
/**
* Account is a function returning a deserialized account, given an address.
*/
export type AccountFn<T = any> = (address: PublicKey) => T;
export type AccountFn<T = any> = AccountProps & ((address: PublicKey) => T);
/**
* Deserialized account owned by a program.
*/
export type ProgramAccount<T = any> = {
publicKey: PublicKey;
account: T;
};
/**
* Non function properties on the acccount namespace.
*/
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>;
};
/**
* Options for an RPC invocation.
@ -112,6 +136,9 @@ export type State = {
rpc: Rpcs;
};
// Tracks all subscriptions.
const subscriptions: Map<string, Subscription> = new Map();
/**
* RpcFactory builds an Rpcs object for a given IDL.
*/
@ -124,177 +151,150 @@ export class RpcFactory {
public static build(
idl: Idl,
coder: Coder,
programId: PublicKey
programId: PublicKey,
provider: Provider
): [Rpcs, Ixs, Txs, Accounts, State] {
const idlErrors = parseIdlErrors(idl);
const rpcs: Rpcs = {};
const ixFns: Ixs = {};
const txFns: Txs = {};
const state = RpcFactory.buildState(idl, coder, programId, idlErrors);
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);
const name = camelCase(idlIx.name);
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)
? RpcFactory.buildAccounts(idl, coder, programId, provider)
: {};
return [rpcs, ixFns, txFns, accountFns, state];
}
// Builds the state namespace.
private static buildState(
idl: Idl,
coder: Coder,
programId: PublicKey,
idlErrors: Map<number, string>
idlErrors: Map<number, string>,
provider: Provider
): State | undefined {
if (idl.state === undefined) {
return undefined;
}
let address = async () => {
let [registrySigner, _nonce] = await PublicKey.findProgramAddress(
[],
programId
);
return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
};
const rpc: Rpcs = {};
idl.state.methods.forEach((m: IdlStateMethod) => {
if (m.name === "new") {
// Ctor `new` method.
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
const tx = new Transaction();
const [programSigner, _nonce] = await PublicKey.findProgramAddress(
[],
programId
);
const ix = new TransactionInstruction({
keys: [
{
pubkey: getProvider().wallet.publicKey,
isWritable: false,
isSigner: true,
},
{ pubkey: await address(), 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,
},
].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts)),
programId,
data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
});
tx.add(ix);
const provider = getProvider();
if (provider === null) {
throw new Error("Provider not found");
}
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;
}
};
} else {
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
validateAccounts(m.accounts, ctx.accounts);
const tx = new Transaction();
const keys = [
{ pubkey: await address(), isWritable: true, isSigner: false },
].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts));
tx.add(
new TransactionInstruction({
keys,
programId,
data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
})
);
const provider = getProvider();
if (provider === null) {
throw new Error("Provider not found");
}
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;
}
};
}
});
// Fetches the state object from the blockchain.
const state = async (): Promise<any> => {
const addr = await address();
const provider = getProvider();
if (provider === null) {
throw new Error("Provider not set");
}
const addr = await programStateAddress(programId);
const accountInfo = await provider.connection.getAccountInfo(addr);
if (accountInfo === null) {
throw new Error(`Entity does not exist ${address}`);
throw new Error(`Account does not exist ${addr.toString()}`);
}
// Assert the account discriminator is correct.
const expectedDiscriminator = Buffer.from(
(
await sha256(`state:${idl.state.struct.name}`, {
outputFormat: "buffer",
})
).slice(0, 8)
const expectedDiscriminator = await stateDiscriminator(
idl.state.struct.name
);
const discriminator = accountInfo.data.slice(0, 8);
if (expectedDiscriminator.compare(discriminator)) {
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
throw new Error("Invalid account discriminator");
}
// Chop off the discriminator before decoding.
const data = accountInfo.data.slice(8);
return coder.state.decode(data);
return coder.state.decode(accountInfo.data);
};
state["address"] = address;
// Namespace with all rpc functions.
const rpc: Rpcs = {};
idl.state.methods.forEach((m: IdlStateMethod) => {
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
const keys = await stateInstructionKeys(programId, provider, m, ctx);
const tx = new Transaction();
tx.add(
new TransactionInstruction({
keys: keys.concat(
RpcFactory.accountsArray(ctx.accounts, m.accounts)
),
programId,
data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
})
);
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;
// Calculates the address of the program's global state object account.
state["address"] = async (): Promise<PublicKey> =>
programStateAddress(programId);
// Subscription singleton.
let sub: null | Subscription = null;
// Subscribe to account changes.
state["subscribe"] = (commitment?: Commitment): EventEmitter => {
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.
private static buildIx(
idlIx: IdlInstruction,
coder: Coder,
@ -357,22 +357,21 @@ export class RpcFactory {
.flat();
}
// Builds the rpc namespace.
private static buildRpc(
idlIx: IdlInstruction,
txFn: TxFn,
idlErrors: Map<number, string>
idlErrors: Map<number, string>,
provider: Provider
): RpcFn {
const rpc = async (...args: any[]): Promise<TransactionSignature> => {
const tx = txFn(...args);
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]);
const provider = getProvider();
if (provider === null) {
throw new Error("Provider not found");
}
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;
@ -384,6 +383,7 @@ export class RpcFactory {
return rpc;
}
// Builds the transaction namespace.
private static buildTx(idlIx: IdlInstruction, ixFn: IxFn): TxFn {
const txFn = (...args: any[]): Transaction => {
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]);
@ -398,52 +398,48 @@ export class RpcFactory {
return txFn;
}
// Returns the generated accounts namespace.
private static buildAccounts(
idl: Idl,
coder: Coder,
programId: PublicKey
programId: PublicKey,
provider: Provider
): Accounts {
const accountFns: Accounts = {};
idl.accounts.forEach((idlAccount) => {
const accountFn = async (address: PublicKey): Promise<any> => {
const provider = getProvider();
if (provider === null) {
throw new Error("Provider not set");
}
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);
if (accountInfo === null) {
throw new Error(`Entity does not exist ${address}`);
throw new Error(`Account does not exist ${address.toString()}`);
}
// Assert the account discriminator is correct.
const expectedDiscriminator = Buffer.from(
(
await sha256(`account:${idlAccount.name}`, {
outputFormat: "buffer",
})
).slice(0, 8)
);
const discriminator = accountInfo.data.slice(0, 8);
if (expectedDiscriminator.compare(discriminator)) {
const discriminator = await accountDiscriminator(idlAccount.name);
if (discriminator.compare(accountInfo.data.slice(0, 8))) {
throw new Error("Invalid account discriminator");
}
// Chop off the discriminator before decoding.
const data = accountInfo.data.slice(8);
return coder.accounts.decode(idlAccount.name, data);
return coder.accounts.decode(idlAccount.name, accountInfo.data);
};
const name = camelCase(idlAccount.name);
accountFns[name] = accountFn;
const size = ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
// Returns the size of the account.
// @ts-ignore
accountFns[name]["size"] = size;
accountsNamespace["size"] =
ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
// Returns an instruction for creating this account.
// @ts-ignore
accountFns[name]["createInstruction"] = async (
accountsNamespace["createInstruction"] = async (
account: Account,
sizeOverride?: number
): Promise<TransactionInstruction> => {
const provider = getProvider();
// @ts-ignore
const size = accountsNamespace["size"];
return SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: account.publicKey,
@ -454,11 +450,102 @@ export class RpcFactory {
programId,
});
};
// Subscribes to all changes to this account.
// @ts-ignore
accountsNamespace["subscribe"] = (
address: PublicKey,
commitment?: Commitment
): EventEmitter => {
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: PublicKey) => {
let sub = subscriptions.get(address.toString());
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?: Buffer
): Promise<ProgramAccount<any>[]> => {
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),
};
})
);
};
accountFns[name] = accountsNamespace;
});
return accountFns;
}
}
type Subscription = {
listener: number;
ee: EventEmitter;
};
function translateError(
idlErrors: Map<number, string>,
err: any
@ -550,84 +637,62 @@ function validateInstruction(ix: IdlInstruction, ...args: any[]) {
// todo
}
function accountSize(idl: Idl, idlAccount: IdlTypeDef): number | undefined {
if (idlAccount.type.kind === "enum") {
let variantSizes = idlAccount.type.variants.map(
(variant: IdlEnumVariant) => {
if (variant.fields === undefined) {
return 0;
}
// @ts-ignore
return (
variant.fields
// @ts-ignore
.map((f: IdlField | IdlType) => {
// @ts-ignore
if (f.name === undefined) {
throw new Error("Tuple enum variants not yet implemented.");
}
// @ts-ignore
return typeSize(idl, f.type);
})
.reduce((a: number, b: number) => a + b)
);
}
// Calculates the deterministic address of the program's "state" account.
async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
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: PublicKey,
provider: Provider,
m: IdlStateMethod,
ctx: RpcContext
) {
if (m.name === "new") {
// Ctor `new` method.
const [programSigner, _nonce] = await PublicKey.findProgramAddress(
[],
programId
);
return Math.max(...variantSizes) + 1;
}
if (idlAccount.type.fields === undefined) {
return 0;
}
return idlAccount.type.fields
.map((f) => typeSize(idl, f.type))
.reduce((a, b) => a + b);
}
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,
},
// Returns the size of the type in bytes. For variable length types, just return
// 1. Users should override this value in such cases.
function typeSize(idl: Idl, ty: IdlType): number {
switch (ty) {
case "bool":
return 1;
case "u8":
return 1;
case "i8":
return 1;
case "u16":
return 2;
case "u32":
return 4;
case "u64":
return 8;
case "i64":
return 8;
case "bytes":
return 1;
case "string":
return 1;
case "publicKey":
return 32;
default:
// @ts-ignore
if (ty.vec !== undefined) {
return 1;
}
// @ts-ignore
if (ty.option !== undefined) {
// @ts-ignore
return 1 + typeSize(ty.option);
}
// @ts-ignore
if (ty.defined !== undefined) {
// @ts-ignore
const filtered = idl.types.filter((t) => t.name === ty.defined);
if (filtered.length !== 1) {
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
}
let typeDef = filtered[0];
return accountSize(idl, typeDef);
}
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
{ pubkey: programId, isWritable: false, isSigner: false },
{
pubkey: SYSVAR_RENT_PUBKEY,
isWritable: false,
isSigner: false,
},
];
} else {
validateAccounts(m.accounts, ctx.accounts);
return [
{
pubkey: await programStateAddress(programId),
isWritable: true,
isSigner: false,
},
];
}
}

117
ts/src/utils.ts Normal file
View File

@ -0,0 +1,117 @@
import * as bs58 from "bs58";
import { sha256 } from "crypto-hash";
import { struct } from "superstruct";
import assert from "assert";
import { PublicKey, AccountInfo, Connection } from "@solana/web3.js";
export const TOKEN_PROGRAM_ID = new PublicKey(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
);
async function getMultipleAccounts(
connection: Connection,
publicKeys: PublicKey[]
): Promise<
Array<null | { publicKey: PublicKey; account: AccountInfo<Buffer> }>
> {
const args = [publicKeys.map((k) => k.toBase58()), { commitment: "recent" }];
// @ts-ignore
const unsafeRes = await connection._rpcRequest("getMultipleAccounts", args);
const res = GetMultipleAccountsAndContextRpcResult(unsafeRes);
if (res.error) {
throw new Error(
"failed to get info about accounts " +
publicKeys.map((k) => k.toBase58()).join(", ") +
": " +
res.error.message
);
}
assert(typeof res.result !== "undefined");
const accounts: Array<null | {
executable: any;
owner: PublicKey;
lamports: any;
data: Buffer;
}> = [];
for (const account of res.result.value) {
let value: {
executable: any;
owner: PublicKey;
lamports: any;
data: Buffer;
} | null = null;
if (account === null) {
accounts.push(null);
continue;
}
if (res.result.value) {
const { executable, owner, lamports, data } = account;
assert(data[1] === "base64");
value = {
executable,
owner: new PublicKey(owner),
lamports,
data: Buffer.from(data[0], "base64"),
};
}
if (value === null) {
throw new Error("Invalid response");
}
accounts.push(value);
}
return accounts.map((account, idx) => {
if (account === null) {
return null;
}
return {
publicKey: publicKeys[idx],
account,
};
});
}
function jsonRpcResult(resultDescription: any) {
const jsonRpcVersion = struct.literal("2.0");
return struct.union([
struct({
jsonrpc: jsonRpcVersion,
id: "string",
error: "any",
}),
struct({
jsonrpc: jsonRpcVersion,
id: "string",
error: "null?",
result: resultDescription,
}),
]);
}
function jsonRpcResultAndContext(resultDescription: any) {
return jsonRpcResult({
context: struct({
slot: "number",
}),
value: resultDescription,
});
}
const AccountInfoResult = struct({
executable: "boolean",
owner: "string",
lamports: "number",
data: "any",
rentEpoch: "number?",
});
const GetMultipleAccountsAndContextRpcResult = jsonRpcResultAndContext(
struct.array([struct.union(["null", AccountInfoResult])])
);
const utils = {
bs58,
sha256,
getMultipleAccounts,
};
export default utils;

View File

@ -12,7 +12,8 @@ export default new Proxy({} as any, {
const process = require("process");
if (typeof window !== "undefined") {
throw new Error("`anchor.workspace` is not available in the browser");
// Workspaces aren't available in the browser, yet.
return undefined;
}
if (!_populatedWorkspace) {

View File

@ -241,7 +241,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1":
"@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@ -665,35 +665,6 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/common@^0.0.1-beta.0":
version "0.0.1-beta.0"
resolved "https://registry.yarnpkg.com/@project-serum/common/-/common-0.0.1-beta.0.tgz#6a2ab2a0adbd19294048e5e96ad695445a959319"
integrity sha512-qJVZIrWQtF24wAfyKnsYhqA698wOMCVEigv/bqpld1n2C7EV2srE7H4tLkTKJxWexeFQOzD4/VOiSfebtc72xQ==
dependencies:
"@project-serum/serum" "^0.13.17"
bn.js "^5.1.2"
superstruct "0.8.3"
"@project-serum/lockup@^0.0.1-beta.0":
version "0.0.1-beta.0"
resolved "https://registry.yarnpkg.com/@project-serum/lockup/-/lockup-0.0.1-beta.0.tgz#184ff0800d980c377ad00c75a09f3f50f42c30fc"
integrity sha512-iSRQppQKidWax+XUJdFsY+P2hefTpwm61ucqM0yHPh4Wnm85s64jaPU531C+8m3lcSC24ynJ2MrP58VvD4Rzfg==
dependencies:
"@project-serum/borsh" "^0.0.1-beta.0"
"@project-serum/common" "^0.0.1-beta.0"
"@solana/spl-token" "0.0.11"
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/serum@^0.13.17":
version "0.13.17"
resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.17.tgz#a2d506c87d094635ff66e4d918b8382d7e9aa78d"
integrity sha512-Q4LMCALOXe/izvS5gICksYWOrrOZkzg2M3sxe4jOG5lrxmcCOQzCoISJ5u0t/LrnXpveJMU4BAVRmNOUBmSbLw==
dependencies:
"@solana/web3.js" "0.86.1"
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@sinonjs/commons@^1.7.0":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@ -708,27 +679,15 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@solana/spl-token@0.0.11":
version "0.0.11"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.0.11.tgz#5b978d38022beccaafac8b1dd1f3f575a39ebd1b"
integrity sha512-Upp+x5B18UegyoDfDJFbkhtL5ynuJBe1AFCbhbSasp+fQP4xx9Lhq+w3rheZPPOnCYwgRfAodABwCO/j7ZFKPg==
dependencies:
"@babel/runtime" "^7.10.5"
"@solana/web3.js" "^0.78.0"
bn.js "^5.0.0"
buffer-layout "^1.2.0"
dotenv "8.2.0"
mkdirp "1.0.4"
"@solana/web3.js@0.86.1":
version "0.86.1"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.86.1.tgz#034a2cef742569f74dfc9960dfbcabc92e674b08"
integrity sha512-9mjWs17ym7PIm7bHA37wnnYyD7rIVHwkx1RI6BzGhMO5h8E+HlZM8ISLgOx+NItg8XRCfFhlrVgJTzK4om1s0g==
"@solana/web3.js@^0.90.4":
version "0.90.4"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.90.4.tgz#9fae2d1fcb5fb3acbcd74a4ac482f2f84b7f7056"
integrity sha512-3HKXIcu+XXsXEFE58Yx6MZkbvSW1Pp0g9Hcvz2o0C5mawh7if2L7abgD5WNdXfIt3M4f6jM+H8fVSuoOJOZjrg==
dependencies:
"@babel/runtime" "^7.3.1"
bn.js "^5.0.0"
bs58 "^4.0.1"
buffer "^5.4.3"
buffer "^6.0.1"
buffer-layout "^1.2.0"
crypto-hash "^1.2.2"
esdoc-inject-style-plugin "^1.0.0"
@ -743,27 +702,6 @@
tweetnacl "^1.0.0"
ws "^7.0.0"
"@solana/web3.js@^0.78.0":
version "0.78.4"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.78.4.tgz#a942d02e353b2c3ff7ddc94cb6846b5a98887bc2"
integrity sha512-Zt6LN35K2sQaQfgWbNjp91qGa6dHccXTQEoojXEo0NqZ/CQqmzretgSI/3kxKUiuvLTY/1WltVM7CKRkwMNRFA==
dependencies:
"@babel/runtime" "^7.3.1"
bn.js "^5.0.0"
bs58 "^4.0.1"
buffer "^5.4.3"
buffer-layout "^1.2.0"
crypto-hash "^1.2.2"
esdoc-inject-style-plugin "^1.0.0"
jayson "^3.0.1"
mz "^2.7.0"
node-fetch "^2.2.0"
npm-run-all "^4.1.5"
rpc-websockets "^7.4.2"
superstruct "^0.8.3"
tweetnacl "^1.0.0"
ws "^7.0.0"
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.12"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
@ -804,6 +742,13 @@
dependencies:
"@types/node" "*"
"@types/bs58@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/bs58/-/bs58-4.0.1.tgz#3d51222aab067786d3bc3740a84a7f5a0effaa37"
integrity sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==
dependencies:
base-x "^3.0.6"
"@types/connect@^3.4.33":
version "3.4.34"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"
@ -812,9 +757,9 @@
"@types/node" "*"
"@types/express-serve-static-core@^4.17.9":
version "4.17.17"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz#6ba02465165b6c9c3d8db3a28def6b16fc9b70f5"
integrity sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ==
version "4.17.18"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40"
integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==
dependencies:
"@types/node" "*"
"@types/qs" "*"
@ -860,9 +805,9 @@
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/lodash@^4.14.159":
version "4.14.166"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.166.tgz#07e7f2699a149219dbc3c35574f126ec8737688f"
integrity sha512-A3YT/c1oTlyvvW/GQqG86EyqWNrT/tisOIh2mW3YCgcx71TNjiTZA3zYZWA5BCmtsOTXjhliy4c4yEkErw6njA==
version "4.14.168"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
"@types/minimist@^1.2.0":
version "1.2.1"
@ -875,9 +820,9 @@
integrity sha512-G0lD1/7qD60TJ/mZmhog76k7NcpLWkPVGgzkRy3CTlnFu4LUQh5v2Wa661z6vnXmD8EQrnALUyf0VRtrACYztw==
"@types/node@^12.12.54":
version "12.19.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.11.tgz#9220ab4b20d91169eb78f456dbfcbabee89dfb50"
integrity sha512-bwVfNTFZOrGXyiQ6t4B9sZerMSShWNsGRw8tC5DY1qImUNczS9SjT4G6PnzjCnxsu5Ubj6xjL2lgwddkxtQl5w==
version "12.19.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.15.tgz#0de7e978fb43db62da369db18ea088a63673c182"
integrity sha512-lowukE3GUI+VSYSu6VcBXl14d61Rp5hA1D+61r16qnwC0lYNSqdxcvRh0pswejorHfS+HgwBasM8jLXz0/aOsw==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
@ -1286,7 +1231,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base-x@^3.0.2:
base-x@^3.0.2, base-x@^3.0.6:
version "3.0.8"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
@ -1405,18 +1350,18 @@ buffer-layout@^1.2.0:
resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.0.tgz#ee1f5ef05a8afd5db6b3a8fe2056c111bc69c737"
integrity sha512-iiyRoho/ERzBUv6kFvfsrLNgTlVwOkqQcSQN7WrO3Y+c5SeuEhCn6+y1KwhM0V3ndptF5mI/RI44zkw0qcR5Jg==
buffer@^5.4.3:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
buffer@^6.0.1:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
ieee754 "^1.2.1"
bufferutil@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.2.tgz#79f68631910f6b993d870fc77dc0a2894eb96cd5"
integrity sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==
version "4.0.3"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.3.tgz#66724b756bed23cd7c28c4d306d7994f9943cc6b"
integrity sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==
dependencies:
node-gyp-build "^4.2.0"
@ -1435,13 +1380,13 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
call-bind@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce"
integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
dependencies:
function-bind "^1.1.1"
get-intrinsic "^1.0.0"
get-intrinsic "^1.0.2"
caller-callsite@^2.0.0:
version "2.0.0"
@ -2050,11 +1995,6 @@ dot-prop@^3.0.0:
dependencies:
is-obj "^1.0.0"
dotenv@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -2106,9 +2046,9 @@ entities@^1.1.1, entities@~1.1.1:
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
error-ex@^1.3.1:
version "1.3.2"
@ -2118,22 +2058,24 @@ error-ex@^1.3.1:
is-arrayish "^0.2.1"
es-abstract@^1.18.0-next.1:
version "1.18.0-next.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
version "1.18.0-next.2"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2"
integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==
dependencies:
call-bind "^1.0.2"
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.2"
is-negative-zero "^2.0.0"
is-negative-zero "^2.0.1"
is-regex "^1.1.1"
object-inspect "^1.8.0"
object-inspect "^1.9.0"
object-keys "^1.1.1"
object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
object.assign "^4.1.2"
string.prototype.trimend "^1.0.3"
string.prototype.trimstart "^1.0.3"
es-to-primitive@^1.2.1:
version "1.2.1"
@ -2600,10 +2542,10 @@ get-caller-file@^2.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49"
integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==
get-intrinsic@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.0.tgz#892e62931e6938c8a23ea5aaebcfb67bd97da97e"
integrity sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
@ -2892,7 +2834,7 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
ieee754@^1.1.13:
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -3103,7 +3045,7 @@ is-glob@^4.0.0, is-glob@^4.0.1:
dependencies:
is-extglob "^2.1.1"
is-negative-zero@^2.0.0:
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
@ -4249,7 +4191,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@1.0.4, mkdirp@1.x:
mkdirp@1.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@ -4436,7 +4378,7 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-inspect@^1.8.0:
object-inspect@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
@ -4453,7 +4395,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.assign@^4.1.1:
object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
@ -5401,7 +5343,7 @@ string.prototype.padend@^3.0.0:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.1"
string.prototype.trimend@^1.0.1:
string.prototype.trimend@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b"
integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==
@ -5409,7 +5351,7 @@ string.prototype.trimend@^1.0.1:
call-bind "^1.0.0"
define-properties "^1.1.3"
string.prototype.trimstart@^1.0.1:
string.prototype.trimstart@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa"
integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==
@ -5484,14 +5426,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
superstruct@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.8.3.tgz#fb4d8901aca3bf9f79afab1bbab7a7f335cc4ef2"
integrity sha512-LbtbFpktW1FcwxVIJlxdk7bCyBq/GzOx2FSFLRLTUhWIA1gHkYPIl3aXRG5mBdGZtnPNT6t+4eEcLDCMOuBHww==
dependencies:
kind-of "^6.0.2"
tiny-invariant "^1.0.6"
superstruct@^0.8.3:
version "0.8.4"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.8.4.tgz#478a19649f6b02c6319c02044db6a1f5863c391f"
@ -5844,9 +5778,9 @@ use@^3.1.0:
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
utf-8-validate@^5.0.2:
version "5.0.3"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.3.tgz#3b64e418ad2ff829809025fdfef595eab2f03a27"
integrity sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==
version "5.0.4"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.4.tgz#72a1735983ddf7a05a43a9c6b67c5ce1c910f9b8"
integrity sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==
dependencies:
node-gyp-build "^4.2.0"