pushing to mvp

This commit is contained in:
spacemandev 2022-08-23 13:06:01 -05:00
parent d00934ff4a
commit 3b11db7922
19 changed files with 1854 additions and 28 deletions

View File

@ -2,14 +2,14 @@
seeds = false
skip-lint = false
[programs.localnet]
solana = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
solana = "BHz6MJGvo8PJaBFqaxyzgJYdY6o8h1rBgsRrUmnHCU9k"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "localnet"
wallet = "/Users/spacemandev/.config/solana/id.json"
wallet = "keypairs/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

1407
projects/xmint/chains/solana/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
[207,6,74,237,83,103,155,107,243,87,192,172,91,150,83,92,138,194,136,23,60,90,51,51,142,48,178,184,159,126,196,158,77,189,167,128,127,127,173,88,168,41,138,40,190,154,145,194,12,102,16,23,160,207,23,147,193,110,161,238,205,173,210,96]

View File

@ -0,0 +1 @@
[167,244,33,68,158,214,219,206,148,80,70,142,118,205,208,137,57,44,221,108,101,92,113,5,142,128,75,145,244,155,12,19,152,238,183,115,211,175,106,200,92,43,97,23,55,112,13,86,192,208,116,175,234,72,251,52,249,173,89,230,190,168,146,31]

View File

@ -0,0 +1,18 @@
use anchor_lang::prelude::*;
#[account]
pub struct Config {
pub owner: Pubkey,
pub nonce: u64,
}
#[account]
#[derive(Default)]
pub struct EmitterAddrAccount{
pub chain_id: u16,
pub emitter_addr: String
}
//Empty account, we just need to check that it *exists*
#[account]
pub struct ProcessedVAA {}

View File

@ -0,0 +1,37 @@
use anchor_lang::prelude::*;
use crate::account::*;
#[derive(Accounts)]
pub struct Initialize<'info>{
#[account(
init,
seeds=[b"config"],
payer=owner,
bump,
space=8+32+8+1
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>
}
#[derive(Accounts)]
#[instruction(chain_id:u16, emitter_addr:String)]
pub struct RegisterChain<'info> {
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
#[account(
constraint = config.owner == owner.key()
)]
pub config: Account<'info, Config>,
#[account(
init,
seeds=[b"EmitterAddress".as_ref(), chain_id.to_be_bytes().as_ref()],
payer=owner,
bump,
space=8+2+256
)]
pub emitter_acc: Account<'info, EmitterAddrAccount>,
}

View File

@ -1,15 +1,34 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
mod account;
mod context;
mod constant;
mod error;
mod event;
mod portal;
mod wormhole;
use context::*;
declare_id!("BHz6MJGvo8PJaBFqaxyzgJYdY6o8h1rBgsRrUmnHCU9k");
#[program]
pub mod solana {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.config.owner = ctx.accounts.owner.key();
ctx.accounts.config.nonce = 0;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
//Register Application Contracts
pub fn register_chain(ctx:Context<RegisterChain>, chain_id:u16, emitter_addr:String) -> Result<()> {
ctx.accounts.emitter_acc.chain_id = chain_id;
ctx.accounts.emitter_acc.emitter_addr = emitter_addr;
Ok(())
}
//Submit Foreign Purchase
}

View File

@ -1,4 +1,4 @@
import * as fs from 'fs';
import fs from 'fs';
import {
attestFromEth,
createWrappedOnEth,
@ -41,7 +41,7 @@ export async function deploy(src: string){
)
if (stderr) {
throw new Error(stderr.message);
throw new Error(stderr);
}
let deploymentAddress:string;
@ -51,12 +51,12 @@ export async function deploy(src: string){
.split("Deployed to: ")[1]
.split("\n")[0]
.trim();
const emittedVAAs = []; //Resets the emittedVAAs
fs.writeFileSync(
`./deployinfo/${src}.deploy.json`,
JSON.stringify({
address: deploymentAddress,
vaas: emittedVAAs
tokenAddress: deploymentAddress,
vaas: []
}, null, 4)
);
}
@ -117,7 +117,7 @@ export async function registerApp(src:string, target:string){
// Alongside registering the App, go ahead register the tokens with one another
// Register target token with src chain
console.log(`Registering ${target} token on ${src}`);
console.log(`Attesting ${target} token on ${src}`);
switch(targetNetwork.type){
case 'evm':
await attest(target, src);
@ -137,7 +137,6 @@ export async function registerApp(src:string, target:string){
* @param address
*/
export async function attest(src: string, target: string, address:string = null){
//Check TARGET type == EVM, else throw error
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
@ -148,8 +147,6 @@ export async function attest(src: string, target: string, address:string = null)
const srcSigner = new ethers.Wallet(srcKey).connect(
new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
);
console.log(`Attesting ${src} Network Token on ${target} Network`)
if(!address){
address = srcDeployInfo.address;
@ -211,12 +208,8 @@ async function fetchVaa(src:string, tx:ethers.ethers.ContractReceipt, portal:boo
const srcNetwork = config.networks[src];
const srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const seq = parseSequenceFromLogEth(tx, srcNetwork['bridgeAddress']);
let emitterAddr = "";
if(portal){
emitterAddr = getEmitterAddressEth(srcNetwork['tokenBridgeAddress']);
} else {
emitterAddr = getEmitterAddressEth(srcDeploymentInfo['address']);
}
const emitterAddr = portal ? getEmitterAddressEth(srcNetwork.tokenBridgeAddress) : getEmitterAddressEth(srcDeploymentInfo.address);
await new Promise((r) => setTimeout(r, 5000)); //wait for Guardian to pick up message
console.log(
"Searching for: ",

View File

@ -1,10 +1,295 @@
import fs from 'fs';
import fetch from 'node-fetch';
import { promisify } from 'util';
import * as evm from './evm';
import * as anchor from '@project-serum/anchor';
import { Solana as SolanaTypes} from '../chains/solana/target/types/solana';
import { findProgramAddressSync } from '@project-serum/anchor/dist/cjs/utils/pubkey';
import {
attestFromSolana,
createWrappedOnSolana,
getEmitterAddressEth,
getEmitterAddressSolana,
getForeignAssetSolana,
parseSequenceFromLogSolana,
setDefaultWasm,
tryNativeToUint8Array
} from '@certusone/wormhole-sdk';
import * as byteify from 'byteify';
import {
getAccount,
getOrCreateAssociatedTokenAccount,
createMint
} from '@solana/spl-token'
const exec = promisify(require('child_process').exec);
const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString());
const IDL = JSON.parse(fs.readFileSync('./chains/solana/target/idl/solana.json').toString());
const CONTRACT_ADDRESS = "BHz6MJGvo8PJaBFqaxyzgJYdY6o8h1rBgsRrUmnHCU9k";
export async function deploy(src: string){
const rpc = config.networks[src].rpc;
const core = config.networks[src].bridgeAddress;
const token = config.networks[src].tokenBridgeAddress;
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString()))));
const connection = new anchor.web3.Connection(rpc);
// Request Airdrop for saved keypair
console.log("Requesting Funds...");
await connection.requestAirdrop(srcKey.publicKey, 1e9*1000); //request 1000 SOL
await new Promise((r) => setTimeout(r, 20000)); //wait for the airdrop to go through
console.log(`${srcKey.publicKey.toString()} balance: `, await (connection.getBalance(srcKey.publicKey)));
//Compile and Deploy Solana Code
console.log("Compiling and deploying solana code takes a min, hang on...");
const cmd = `cd chains/solana && solana config set -u ${rpc} -k keypairs/id.json && anchor build && anchor deploy --program-name solana --program-keypair keypairs/solana-keypair.json && exit`
const {stdout, stderr} = await exec (cmd);
console.log(stdout);
// Also initalize the contract
await new Promise((r) => setTimeout(r, 15000)) // wait for the chain to recognize the program
//Initalize the Contract
const xmint = new anchor.Program<SolanaTypes>(
IDL,
CONTRACT_ADDRESS,
new anchor.AnchorProvider(
new anchor.web3.Connection(rpc),
new anchor.Wallet(srcKey),
{}));
const [configAcc, _] = findProgramAddressSync([
Buffer.from("config")
], xmint.programId);
await xmint.methods
.initialize()
.accounts({
config: configAcc,
owner: xmint.provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
})
.rpc();
// Deploy the SPL Token for Xmint
const mint = await createMint(
connection,
srcKey,
xmint.programId,
xmint.programId,
9 // We are using 9 to match the CLI decimal default exactly
);
// Store deploy info
fs.writeFileSync(`./deployinfo/${src}.deploy.json`, JSON.stringify({
address: CONTRACT_ADDRESS,
tokenAddress: mint,
vaas: []
}, null, 4));
}
/**
* Attest token from src chain and create wrapped on target chain
* @param src
* @param target
* @param address
*/
export async function attest(src: string, target:string, address:string = null){
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
)));
const connection = new anchor.web3.Connection(srcNetwork.rpc);
// If we don't pass in an address (like for WSOL) we assume we want to attest the xmint token
if(!address){
address = srcDeployInfo.tokenAddress;
}
console.log(`Attesting ${address} from ${target} network onto ${src}`);
const tx = await attestFromSolana(
connection,
srcNetwork.bridgeAddress,
srcNetwork.tokenBridgeAddress,
srcKey.publicKey.toString(),
address
)
const attestVaa = await fetchVaa(src, tx, true);
switch(targetNetwork.type){
case "evm":
await evm.createWrapped(target, src, attestVaa);
break;
case "solana":
await createWrapped(target, src, attestVaa);
}
}
/**
* Fetches the signed VAA from the Gaurdian. Will use xmint as emitter if portal=false
* @param src
* @param tx
* @param portal
*/
async function fetchVaa(src:string, tx, portal:boolean=false):Promise<string>{
const srcNetwork = config.networks[src];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
)));
const connection = new anchor.web3.Connection(srcNetwork.rpc);
setDefaultWasm("node");
const emitterAddr = portal ? await getEmitterAddressSolana(srcNetwork.tokenBridgeAddress) : await getEmitterAddressSolana(srcDeployInfo.address);
const seq = parseSequenceFromLogSolana(await connection.getTransaction(tx));
await new Promise((r) => setTimeout(r, 5000)); //wait for gaurdian to pick up messsage
console.log(
"Searching for: ",
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
);
const vaaBytes = await (
await fetch(
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
)
).json();
if(!vaaBytes['vaaBytes']){
throw new Error("VAA not found!");
}
console.log("VAA Found: ", vaaBytes.vaaBytes);
return vaaBytes.vaaBytes;
}
export async function deploy(src: string){}
export async function attest(src: string, target:string, address:string = null){}
export async function registerApp(src:string, target:string){}
export async function createWrapped(src:string, target:string, vaa:string){}
export async function createWrapped(src:string, target:string, vaa:string){
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
)));
const connection = new anchor.web3.Connection(srcNetwork.rpc);
const tx = await createWrappedOnSolana(
connection,
srcNetwork.bridgeAddress,
srcNetwork.tokenBridgeAddress,
srcKey.publicKey.toString(),
Buffer.from(vaa, "base64")
);
await new Promise((r) => setTimeout(r, 5000)); // wait for blocks to advance before fetching new foreign address
const foreignAddress = await getForeignAssetSolana(
connection,
srcNetwork.tokenBridgeAddress,
targetNetwork.wormholeChainId,
tryNativeToUint8Array(targetDeployInfo.address, targetNetwork.wormholeChainId)
);
console.log(`${src} Network has new PortalWrappedToken for ${target} network at ${foreignAddress}`);
}
export async function registerApp(src:string, target:string){
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
)));
const connection = new anchor.web3.Connection(srcNetwork.rpc);
let targetEmitter;
switch (targetNetwork.type){
case 'evm':
targetEmitter = getEmitterAddressEth(targetDeployInfo.address);
break;
case 'solana':
targetEmitter = await getEmitterAddressSolana(targetDeployInfo.address);
break;
}
const xmint = new anchor.Program<SolanaTypes>(
IDL,
CONTRACT_ADDRESS,
new anchor.AnchorProvider(
connection,
new anchor.Wallet(srcKey),
{}));
const [emitterAcc, emitterBmp] = findProgramAddressSync([
Buffer.from("EmitterAddress"),
byteify.serializeUint16(targetNetwork.wormholeChainId)
], xmint.programId);
const tx = await xmint.methods
.registerChain(
targetNetwork.wormholeChainId,
targetEmitter
)
.accounts({
emitterAcc: emitterAcc,
owner: xmint.provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
})
.rpc();
console.log(`Registered ${target} contract on ${src}`);
// Alongside registering the App, go ahead register the tokens with one another
// Register target token with src chain
console.log(`Attesting ${target} token on ${src}`);
switch(targetNetwork.type){
case "evm":
await evm.attest(target, src);
break;
case "solana":
await attest(target, src);
break;
}
console.log(`Attested ${target} token on ${src}`);
}
export async function submitForeignPurchase(src:string, target:string, vaa:string){}
export async function submitForeignSale(src:string, target:string, vaa:string){}
export async function buyToken(src:string, target: string, amount:number){}
export async function sellToken(src:string, target:string, amount:number){}
export async function sellToken(src:string, target:string, amount:number){}
export async function balance(src: string, target: string) : Promise<string>{
const srcNetwork = config.networks[src];
const targetNetwork = config.networks[target];
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
)));
const connection = new anchor.web3.Connection(srcNetwork.rpc);
if (src == target) {
//Get Native Currency
return (await connection.getBalance(srcKey.publicKey)).toString()
}
// Else get the Token Balance of the Foreign Network's token on the Src Network
const foreignAddress = await getForeignAssetSolana(
connection,
srcNetwork.tokenBridgeAddress,
targetNetwork.wormholeChainId,
tryNativeToUint8Array(targetDeployInfo.address, targetNetwork.wormholeChainId)
);
const tokenAccountAddress = await getOrCreateAssociatedTokenAccount(
connection,
srcKey,
new anchor.web3.PublicKey(foreignAddress),
srcKey.publicKey,
);
const tokenAccount = await getAccount(
connection,
tokenAccountAddress.address
)
return tokenAccount.amount.toString();
}
export async function buyToken(src:string, target: string, amount:number){
}

View File

@ -1 +1 @@
J2D4pwDred8P9ioyPEZVLPht885AeYpifsFGUyuzVmiKQosAvmZP4EegaKFrSprBC5vVP1xTvu61vYDWsxBNsYx
[207,6,74,237,83,103,155,107,243,87,192,172,91,150,83,92,138,194,136,23,60,90,51,51,142,48,178,184,159,126,196,158,77,189,167,128,127,127,173,88,168,41,138,40,190,154,145,194,12,102,16,23,160,207,23,147,193,110,161,238,205,173,210,96]

View File

@ -7,8 +7,10 @@
"dependencies": {
"@certusone/wormhole-sdk": "^0.6.1",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.1",
"@types/node": "^18.7.1",
"@types/node-fetch": "^2.6.2",
"byteify": "^2.0.10",
"commander": "^9.4.0",
"ethers": "^5.6.9",
"node-fetch": "2"

View File

@ -1,3 +1,6 @@
# Build SOL code
cd chains/solana && anchor build && cd ../../
# Deploy the code on EVM0 and SOL0
ts-node orchestrator.ts deploy evm0
ts-node orchestrator.ts deploy sol0

View File

@ -0,0 +1,13 @@
# Deploy the code on EVM0 and SOL0
ts-node orchestrator.ts deploy evm0
ts-node orchestrator.ts deploy sol0
# Print Balances for EVM0 and SOL0 Keypairs
ts-node orchestrator.ts balance evm0 evm0
ts-node orchestrator.ts balance evm0 sol0
ts-node orchestrator.ts balance sol0 sol0
ts-node orchestrator.ts balance sol0 evm0
# Register Apps EVM<>SOL
ts-node orchestrator.ts register-app evm0 sol0
ts-node orchestrator.ts register-app sol0 evm0

View File

@ -478,6 +478,16 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@solana/buffer-layout-utils@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca"
integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==
dependencies:
"@solana/buffer-layout" "^4.0.0"
"@solana/web3.js" "^1.32.0"
bigint-buffer "^1.1.5"
bignumber.js "^9.0.1"
"@solana/buffer-layout@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz#75b1b11adc487234821c81dfae3119b73a5fd734"
@ -497,6 +507,15 @@
buffer-layout "^1.2.0"
dotenv "10.0.0"
"@solana/spl-token@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.1.tgz#20ba93f8e86b0913e6bfa49fc0708f7fd3bdaf5e"
integrity sha512-26/0XlW5Lyeu3CUlBGt+0o3l4H6AJtRtMMtsxhcKj+DwfGg+QMnPl/exTmZLEsymsn03PFhogd97v5fJXhYeow==
dependencies:
"@solana/buffer-layout" "^4.0.0"
"@solana/buffer-layout-utils" "^0.2.0"
"@solana/web3.js" "^1.41.0"
"@solana/web3.js@^1.21.0", "@solana/web3.js@^1.24.0", "@solana/web3.js@^1.36.0":
version "1.51.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.51.0.tgz#51b28b5332f1f03ea25bea6c229869e33aebc507"
@ -520,6 +539,29 @@
superstruct "^0.14.2"
tweetnacl "^1.0.3"
"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.41.0":
version "1.53.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.53.0.tgz#24a6341e4026fc2b993656141361c54bec917c07"
integrity sha512-QyQDA9U5b+AiTo1ANsj9WihWWECeLv6VRpiTE7xPe5hLYANXZYecnlLglNiEzVgRg/jLvR5DrCISXhHx/mAEJw==
dependencies:
"@babel/runtime" "^7.12.5"
"@ethersproject/sha2" "^5.5.0"
"@solana/buffer-layout" "^4.0.0"
bigint-buffer "^1.1.5"
bn.js "^5.0.0"
borsh "^0.7.0"
bs58 "^4.0.1"
buffer "6.0.1"
fast-stable-stringify "^1.0.0"
jayson "^3.4.4"
js-sha3 "^0.8.0"
node-fetch "2"
react-native-url-polyfill "^1.3.0"
rpc-websockets "^7.5.0"
secp256k1 "^4.0.2"
superstruct "^0.14.2"
tweetnacl "^1.0.3"
"@terra-money/legacy.proto@npm:@terra-money/terra.proto@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz#59c18f30da10d43200bab3ba8feb5b17e43a365f"
@ -719,7 +761,7 @@ bigint-buffer@^1.1.5:
dependencies:
bindings "^1.3.0"
bignumber.js@^9.0.0:
bignumber.js@^9.0.0, bignumber.js@^9.0.1:
version "9.1.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62"
integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==
@ -843,6 +885,11 @@ bufferutil@^4.0.1, bufferutil@^4.0.3:
dependencies:
node-gyp-build "^4.3.0"
byteify@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/byteify/-/byteify-2.0.10.tgz#2a77702887605439741d10dea4494172925089b4"
integrity sha512-clrE0NtRB/YwjQcmrUU9qpxRIQ5Jc1HGedv6/LWd08upE0FV0S4YvPBkmKEsTaquqGmhx34LkRBO+lXpTgwYgw==
call-bind@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"