diff --git a/cli/src/main.rs b/cli/src/main.rs index 877feefb..d0fae5ba 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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 { Ok(validator_handle) } +// TODO: Testing and deploys should use separate sections of metadata. +// Similarly, each network should have separate metadata. fn deploy(url: Option, keypair: Option) -> 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> { } #[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "PascalCase")] pub struct DeployStdout { program_id: String, } diff --git a/cli/src/template.rs b/cli/src/template.rs index ff455174..e7c9a8ff 100644 --- a/cli/src/template.rs +++ b/cli/src/template.rs @@ -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 = []"# diff --git a/examples/lockup/migrations/deploy.js b/examples/lockup/migrations/deploy.js new file mode 100644 index 00000000..4e05bdd9 --- /dev/null +++ b/examples/lockup/migrations/deploy.js @@ -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; +} diff --git a/examples/lockup/programs/lockup/src/lib.rs b/examples/lockup/programs/lockup/src/lib.rs index 6d24fcd6..e7ddfc29 100644 --- a/examples/lockup/programs/lockup/src/lib.rs +++ b/examples/lockup/programs/lockup/src/lib.rs @@ -131,8 +131,8 @@ pub mod lockup { } // Sends funds from the lockup program to a whitelisted program. - pub fn whitelist_withdraw( - ctx: Context, + pub fn whitelist_withdraw<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, WhitelistWithdraw<'info>>, instruction_data: Vec, 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, + pub fn whitelist_deposit<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, WhitelistDeposit<'info>>, instruction_data: Vec, ) -> 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, ) -> 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) } diff --git a/examples/lockup/programs/registry/src/lib.rs b/examples/lockup/programs/registry/src/lib.rs index bc5213a2..6b26bf0f 100644 --- a/examples/lockup/programs/registry/src/lib.rs +++ b/examples/lockup/programs/registry/src/lib.rs @@ -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, diff --git a/examples/lockup/tests/lockup.js b/examples/lockup/tests/lockup.js index a6980761..ddd4573c 100644 --- a/examples/lockup/tests/lockup.js +++ b/examples/lockup/tests/lockup.js @@ -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; diff --git a/ts/package.json b/ts/package.json index b76a508a..973badaf 100644 --- a/ts/package.json +++ b/ts/package.json @@ -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": { diff --git a/ts/src/coder.ts b/ts/src/coder.ts index 421cf2bb..59836e9e 100644 --- a/ts/src/coder.ts +++ b/ts/src/coder.ts @@ -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(accountName: string, account: T): Buffer { + public async encode( + accountName: string, + account: T + ): Promise { 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(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(account: T): Buffer { + public async encode(name: string, account: T): Promise { 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(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 { + 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 { + 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); +} diff --git a/ts/src/index.ts b/ts/src/index.ts index 08ba75df..0e8c976e 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -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, }; diff --git a/ts/src/program.ts b/ts/src/program.ts index a21bc717..5e8fc510 100644 --- a/ts/src/program.ts +++ b/ts/src/program.ts @@ -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; diff --git a/ts/src/provider.ts b/ts/src/provider.ts new file mode 100644 index 00000000..a9a2c113 --- /dev/null +++ b/ts/src/provider.ts @@ -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, + opts?: ConfirmOptions + ): Promise { + if (signers === undefined) { + signers = []; + } + if (opts === undefined) { + opts = this.opts; + } + + const signerKps = signers.filter((s) => s !== undefined) as Array; + 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, + opts?: ConfirmOptions + ): Promise> { + 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; + 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; +}; + +export interface Wallet { + signTransaction(tx: Transaction): Promise; + signAllTransactions(txs: Transaction[]): Promise; + 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 { + tx.partialSign(this.payer); + return tx; + } + + async signAllTransactions(txs: Transaction[]): Promise { + return txs.map((t) => { + t.partialSign(this.payer); + return t; + }); + } + + get publicKey(): PublicKey { + return this.payer.publicKey; + } +} diff --git a/ts/src/rpc.ts b/ts/src/rpc.ts index 7fcf77b0..529c6896 100644 --- a/ts/src/rpc.ts +++ b/ts/src/rpc.ts @@ -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; /** * 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 = (address: PublicKey) => T; +export type AccountFn = AccountProps & ((address: PublicKey) => T); + +/** + * Deserialized account owned by a program. + */ +export type ProgramAccount = { + publicKey: PublicKey; + account: T; +}; + +/** + * Non function properties on the acccount namespace. + */ +type AccountProps = { + size: number; + all: (filter?: Buffer) => Promise[]>; + subscribe: (address: PublicKey, commitment?: Commitment) => EventEmitter; + unsubscribe: (address: PublicKey) => void; + createInstruction: (account: Account) => Promise; +}; /** * Options for an RPC invocation. @@ -112,6 +136,9 @@ export type State = { rpc: Rpcs; }; +// Tracks all subscriptions. +const subscriptions: Map = 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 + idlErrors: Map, + 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 => { - 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 => { - 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 => { - 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 => { + 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 => + 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 + idlErrors: Map, + provider: Provider ): RpcFn { const rpc = async (...args: any[]): Promise => { 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 => { - 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 => { 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 => { - 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[]> => { + 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, 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 { + 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, + }, + ]; } } diff --git a/ts/src/utils.ts b/ts/src/utils.ts new file mode 100644 index 00000000..4d0addb9 --- /dev/null +++ b/ts/src/utils.ts @@ -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 }> +> { + 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 = []; + 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; diff --git a/ts/src/workspace.ts b/ts/src/workspace.ts index 1674549a..33a5528a 100644 --- a/ts/src/workspace.ts +++ b/ts/src/workspace.ts @@ -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) { diff --git a/ts/yarn.lock b/ts/yarn.lock index 43450554..54f68514 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -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"