Implement pattern from dxns-registry-client
This commit is contained in:
parent
6bb264d69c
commit
19d5405087
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
dist
|
||||||
|
17
package.json
17
package.json
@ -1,11 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "chiba-clonk-client",
|
"name": "chiba-clonk-client",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"repository": "git@github.com:vulcanize/chiba-clonk-client.git",
|
"repository": "git@github.com:vulcanize/chiba-clonk-client.git",
|
||||||
"author": "contact@deepstacksoft.com",
|
"author": "contact@deepstacksoft.com",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/is-url": "^1.2.30",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
|
"@types/ripemd160": "^2.0.0",
|
||||||
|
"@types/secp256k1": "^4.0.3",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"protoc-gen-ts": "^0.8.2",
|
"protoc-gen-ts": "^0.8.2",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
@ -13,14 +17,21 @@
|
|||||||
"typescript": "^4.6.2"
|
"typescript": "^4.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cosmjs/crypto": "^0.28.1",
|
||||||
|
"@cosmjs/encoding": "^0.28.1",
|
||||||
"@cosmjs/proto-signing": "^0.28.0",
|
"@cosmjs/proto-signing": "^0.28.0",
|
||||||
"@cosmjs/stargate": "^0.28.0",
|
"@cosmjs/stargate": "^0.28.0",
|
||||||
"@metamask/eth-sig-util": "^4.0.0",
|
"@metamask/eth-sig-util": "^4.0.0",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
"ethers": "^5.6.1",
|
"ethers": "^5.6.1",
|
||||||
"evmosjs": "^0.2.2"
|
"evmosjs": "^0.2.2",
|
||||||
|
"is-url": "^1.2.4",
|
||||||
|
"js-sha256": "^0.9.0",
|
||||||
|
"ripemd160": "^2.0.2",
|
||||||
|
"secp256k1": "^4.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest"
|
"test": "jest --runInBand",
|
||||||
|
"build": "tsc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
src/account.ts
Normal file
70
src/account.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import { MessageTypes, signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util';
|
||||||
|
import { Secp256k1 } from "@cosmjs/crypto";
|
||||||
|
|
||||||
|
interface TypedMessageDomain {
|
||||||
|
name?: string;
|
||||||
|
version?: string;
|
||||||
|
chainId?: number;
|
||||||
|
verifyingContract?: string;
|
||||||
|
salt?: ArrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry account.
|
||||||
|
*/
|
||||||
|
// TODO(egor): This is a wrapper around the private key and doesn't have any account related stuff (e.g. account number/sequence). Maybe rename to Key?
|
||||||
|
export class Account {
|
||||||
|
_privateKey: Buffer
|
||||||
|
_publicKey?: Uint8Array
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New Account.
|
||||||
|
* @param {buffer} privateKey
|
||||||
|
*/
|
||||||
|
constructor(privateKey: Buffer) {
|
||||||
|
assert(privateKey);
|
||||||
|
|
||||||
|
this._privateKey = privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
get privateKey() {
|
||||||
|
return this._privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init () {
|
||||||
|
// Generate public key.
|
||||||
|
const keypair = await Secp256k1.makeKeypair(this._privateKey);
|
||||||
|
|
||||||
|
const compressed = Secp256k1.compressPubkey(keypair.pubkey);
|
||||||
|
this._publicKey = compressed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get private key.
|
||||||
|
*/
|
||||||
|
getPrivateKey() {
|
||||||
|
return this._privateKey.toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign message.
|
||||||
|
*/
|
||||||
|
sign(message: any) {
|
||||||
|
assert(message);
|
||||||
|
const eipMessageDomain: any = message.eipToSign.domain;
|
||||||
|
|
||||||
|
const signature = signTypedData({
|
||||||
|
data: {
|
||||||
|
types: message.eipToSign.types as MessageTypes,
|
||||||
|
primaryType: message.eipToSign.primaryType,
|
||||||
|
domain: eipMessageDomain as TypedMessageDomain,
|
||||||
|
message: message.eipToSign.message as Record<string, unknown>
|
||||||
|
},
|
||||||
|
privateKey: this._privateKey,
|
||||||
|
version: SignTypedDataVersion.V4
|
||||||
|
})
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
}
|
28
src/bonds.test.ts
Normal file
28
src/bonds.test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Registry } from './index';
|
||||||
|
import { getConfig } from './testing/helper';
|
||||||
|
|
||||||
|
const { mockServer, chibaClonk: { chainId, endpoint, privateKey, accountAddress, fee } } = getConfig();
|
||||||
|
|
||||||
|
jest.setTimeout(90 * 1000);
|
||||||
|
|
||||||
|
const bondTests = () => {
|
||||||
|
let registry: Registry;
|
||||||
|
let bondId1: string;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
registry = new Registry(endpoint, chainId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create bond.', async () => {
|
||||||
|
bondId1 = await registry.getNextBondId(accountAddress);
|
||||||
|
expect(bondId1).toBeDefined();
|
||||||
|
await registry.createBond({ denom: 'aphoton', amount: '100' }, accountAddress, privateKey, fee);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mockServer) {
|
||||||
|
// Required as jest complains if file has no tests.
|
||||||
|
test('skipping bond tests', () => {});
|
||||||
|
} else {
|
||||||
|
describe('Bonds', bondTests);
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import { createBond, sendDeposit, sendTokens, sendVote } from './index'
|
|
||||||
|
|
||||||
const SENDER_ADDRESS = 'ethm1kgwzff36qmx5tvfvfr7wvdurp5mr25csyqxgdm';
|
|
||||||
const SENDER_PRIVATE_KEY = '12e94bcc0daecd936b499f3eeb3b3b76ac1410cbaff2ee6c6f64d768453db0cf';
|
|
||||||
const TO_ADDRESS = 'ethm1e6r855un2ufnne9cdpujvan5srxjand37pepuz';
|
|
||||||
|
|
||||||
test('Send tokens', async () => {
|
|
||||||
await sendTokens(SENDER_PRIVATE_KEY, SENDER_ADDRESS, TO_ADDRESS)
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Gov module', () => {
|
|
||||||
test('Send deposit', async () => {
|
|
||||||
const depositParams = {
|
|
||||||
proposalId: 1,
|
|
||||||
amount: '10',
|
|
||||||
denom: 'aphoton',
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendDeposit(SENDER_PRIVATE_KEY, SENDER_ADDRESS, depositParams)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Send vote', async () => {
|
|
||||||
const voteParams = {
|
|
||||||
proposalId: 1,
|
|
||||||
option: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendVote(SENDER_PRIVATE_KEY, SENDER_ADDRESS, voteParams)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Bond module', () => {
|
|
||||||
test('Create bond', async () => {
|
|
||||||
const bondParams = {
|
|
||||||
amount: '100',
|
|
||||||
denom: 'aphoton',
|
|
||||||
}
|
|
||||||
|
|
||||||
await createBond(SENDER_PRIVATE_KEY, SENDER_ADDRESS, bondParams)
|
|
||||||
})
|
|
||||||
})
|
|
293
src/index.ts
293
src/index.ts
@ -1,199 +1,144 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { MessageTypes, signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
|
import isUrl from 'is-url';
|
||||||
import { generateEndpointAccount, generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider';
|
import { sha256 } from 'js-sha256';
|
||||||
|
import { generatePostBodyBroadcast } from '@tharsis/provider';
|
||||||
import {
|
import {
|
||||||
createMessageSend,
|
|
||||||
createTxRawEIP712,
|
|
||||||
signatureToWeb3Extension,
|
|
||||||
createTxMsgVote,
|
|
||||||
Chain,
|
Chain,
|
||||||
Sender,
|
Sender,
|
||||||
MessageMsgVote
|
Fee,
|
||||||
} from '@tharsis/transactions'
|
} from '@tharsis/transactions'
|
||||||
|
|
||||||
import { createTxMsgDeposit, MessageMsgDeposit } from "./gov";
|
import { createTxMsgCreateBond, MessageMsgCreateBond } from "./bond";
|
||||||
import { createTxMsgCreateBond, createTxMsgRefillBond, MessageMsgCreateBond, MessageMsgRefillBond } from "./bond";
|
import { RegistryClient } from "./registry-client";
|
||||||
|
import { Account } from "./account";
|
||||||
|
import { createTransaction } from "./txbuilder";
|
||||||
|
|
||||||
const ETHERMINT_REST_ENDPOINT = 'http://127.0.0.1:1317'
|
const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.';
|
||||||
|
|
||||||
interface TypedMessageDomain {
|
export const DEFAULT_CHAIN_ID = 'ethermint_9000-1';
|
||||||
name?: string;
|
|
||||||
version?: string;
|
// Parse Tx response from cosmos-sdk.
|
||||||
chainId?: number;
|
export const parseTxResponse = (result: any) => {
|
||||||
verifyingContract?: string;
|
const { txhash: hash, height, ...txResponse } = result;
|
||||||
salt?: ArrayBuffer;
|
txResponse.data = txResponse.data && Buffer.from(txResponse.data, 'base64').toString('utf8');
|
||||||
|
txResponse.log = JSON.parse(txResponse.raw_log);
|
||||||
|
|
||||||
|
txResponse.events.forEach((event:any) => {
|
||||||
|
event.attributes = event.attributes.map(({ key, value }: { key: string, value: string }) => ({
|
||||||
|
key: Buffer.from(key, 'base64').toString('utf8'),
|
||||||
|
value: Buffer.from(value, 'base64').toString('utf8')
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { hash, height, ...txResponse };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isKeyValid = (key: string) => key && key.match(/^[0-9a-fA-F]{64}$/);
|
||||||
|
|
||||||
|
export class Registry {
|
||||||
|
_endpoint: string
|
||||||
|
_chain: Chain
|
||||||
|
_client: RegistryClient
|
||||||
|
|
||||||
|
static processWriteError(error: Error) {
|
||||||
|
/**
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{
|
||||||
|
message: '{"code":18,"data":null,"log":"invalid request: Name already reserved.: failed to execute message; message index: 0","info":"","gasWanted":"200000","gasUsed":"86717","events":[],"codespace":"sdk"}',
|
||||||
|
path: [ 'submit' ]
|
||||||
|
}g
|
||||||
|
*/
|
||||||
|
const message = JSON.parse(error.message);
|
||||||
|
return message.log || DEFAULT_WRITE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendTokens = async (senderPrivateKey: string, senderAddress: string, destinationAddress: string) => {
|
constructor(url: string, cosmosChainId = DEFAULT_CHAIN_ID) {
|
||||||
let { data: addrData} = await axios.get(`${ETHERMINT_REST_ENDPOINT}${generateEndpointAccount(senderAddress)}`)
|
if (!isUrl(url)) {
|
||||||
|
throw new Error('Path to a registry GQL endpoint should be provided.');
|
||||||
|
}
|
||||||
|
|
||||||
const chain = {
|
this._endpoint = url;
|
||||||
|
this._client = new RegistryClient(url);
|
||||||
|
|
||||||
|
this._chain = {
|
||||||
chainId: 9000,
|
chainId: 9000,
|
||||||
cosmosChainId: 'ethermint_9000-1',
|
cosmosChainId
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get account by addresses.
|
||||||
|
*/
|
||||||
|
async getAccount(address: string) {
|
||||||
|
return this._client.getAccount(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the next bondId for the given account private key.
|
||||||
|
*/
|
||||||
|
async getNextBondId(address: string) {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account } = await this.getAccount(address);
|
||||||
|
const accountObj = account.base_account;
|
||||||
|
|
||||||
|
const nextSeq = parseInt(accountObj.sequence, 10) + 1;
|
||||||
|
result = sha256(`${accountObj.address}:${accountObj.number}:${nextSeq}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
const error = err[0] || err;
|
||||||
|
throw new Error(Registry.processWriteError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create bond.
|
||||||
|
*/
|
||||||
|
async createBond(params: MessageMsgCreateBond, senderAddress: string, privateKey: string, fee: Fee) {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
|
||||||
|
|
||||||
const sender = {
|
const sender = {
|
||||||
accountAddress: addrData.account.base_account.address,
|
accountAddress: accountInfo.address,
|
||||||
sequence: addrData.account.base_account.sequence,
|
sequence: accountInfo.sequence,
|
||||||
accountNumber: addrData.account.base_account.account_number,
|
accountNumber: accountInfo.account_number,
|
||||||
pubkey: addrData.account.base_account.pub_key.key,
|
pubkey: accountInfo.pub_key.key,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fee = {
|
const msg = createTxMsgCreateBond(this._chain, sender, fee, '', params)
|
||||||
amount: '20',
|
result = await this._submitTx(msg, privateKey, sender);
|
||||||
denom: 'aphoton',
|
} catch (err: any) {
|
||||||
gas: '200000',
|
const error = err[0] || err;
|
||||||
|
throw new Error(Registry.processWriteError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
const memo = ''
|
return parseTxResponse(result);
|
||||||
|
|
||||||
const params = {
|
|
||||||
destinationAddress: destinationAddress,
|
|
||||||
amount: '10',
|
|
||||||
denom: 'aphoton',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a MsgSend transaction.
|
/**
|
||||||
const msg = createMessageSend(chain, sender, fee, memo, params)
|
* Submit a generic Tx to the chain.
|
||||||
|
*/
|
||||||
await signAndSendMessage(senderPrivateKey, chain, sender, msg)
|
async _submitTx(message: any, privateKey: string, sender: Sender) {
|
||||||
|
// Check private key.
|
||||||
|
if (!isKeyValid(privateKey)) {
|
||||||
|
throw new Error('Registry privateKey should be a hex string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendVote = async (senderPrivateKey: string, senderAddress: string, params: MessageMsgVote) => {
|
// Check that the account exists on-chain.
|
||||||
let { data: addrData} = await axios.get(`${ETHERMINT_REST_ENDPOINT}${generateEndpointAccount(senderAddress)}`)
|
const account = new Account(Buffer.from(privateKey, 'hex'));
|
||||||
|
|
||||||
const chain = {
|
// Generate signed Tx.
|
||||||
chainId: 9000,
|
const transaction = createTransaction(message, account, sender, this._chain);
|
||||||
cosmosChainId: 'ethermint_9000-1',
|
|
||||||
|
const tx = generatePostBodyBroadcast(transaction)
|
||||||
|
|
||||||
|
// Submit Tx to chain.
|
||||||
|
const { tx_response: response } = await this._client.submit(tx);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sender = {
|
|
||||||
accountAddress: addrData.account.base_account.address,
|
|
||||||
sequence: addrData.account.base_account.sequence,
|
|
||||||
accountNumber: addrData.account.base_account.account_number,
|
|
||||||
pubkey: addrData.account.base_account.pub_key.key,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fee = {
|
|
||||||
amount: '20',
|
|
||||||
denom: 'aphoton',
|
|
||||||
gas: '200000',
|
|
||||||
}
|
|
||||||
|
|
||||||
const memo = ''
|
|
||||||
|
|
||||||
const msg = createTxMsgVote(chain, sender, fee, memo, params)
|
|
||||||
await signAndSendMessage(senderPrivateKey, chain, sender, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendDeposit = async (senderPrivateKey: string, senderAddress: string, params: MessageMsgDeposit) => {
|
|
||||||
let { data: addrData} = await axios.get(`${ETHERMINT_REST_ENDPOINT}${generateEndpointAccount(senderAddress)}`)
|
|
||||||
|
|
||||||
const chain = {
|
|
||||||
chainId: 9000,
|
|
||||||
cosmosChainId: 'ethermint_9000-1',
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = {
|
|
||||||
accountAddress: addrData.account.base_account.address,
|
|
||||||
sequence: addrData.account.base_account.sequence,
|
|
||||||
accountNumber: addrData.account.base_account.account_number,
|
|
||||||
pubkey: addrData.account.base_account.pub_key.key,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fee = {
|
|
||||||
amount: '20',
|
|
||||||
denom: 'aphoton',
|
|
||||||
gas: '200000',
|
|
||||||
}
|
|
||||||
|
|
||||||
const memo = ''
|
|
||||||
|
|
||||||
const msg = createTxMsgDeposit(chain, sender, fee, memo, params)
|
|
||||||
await signAndSendMessage(senderPrivateKey, chain, sender, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createBond = async (senderPrivateKey: string, senderAddress: string, params: MessageMsgCreateBond) => {
|
|
||||||
let { data: addrData} = await axios.get(`${ETHERMINT_REST_ENDPOINT}${generateEndpointAccount(senderAddress)}`)
|
|
||||||
|
|
||||||
const chain = {
|
|
||||||
chainId: 9000,
|
|
||||||
cosmosChainId: 'ethermint_9000-1',
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = {
|
|
||||||
accountAddress: addrData.account.base_account.address,
|
|
||||||
sequence: addrData.account.base_account.sequence,
|
|
||||||
accountNumber: addrData.account.base_account.account_number,
|
|
||||||
pubkey: addrData.account.base_account.pub_key.key,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fee = {
|
|
||||||
amount: '20',
|
|
||||||
denom: 'aphoton',
|
|
||||||
gas: '200000',
|
|
||||||
}
|
|
||||||
|
|
||||||
const memo = ''
|
|
||||||
|
|
||||||
const msg = createTxMsgCreateBond(chain, sender, fee, memo, params)
|
|
||||||
await signAndSendMessage(senderPrivateKey, chain, sender, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const refillBond = async (senderPrivateKey: string, senderAddress: string, params: MessageMsgRefillBond) => {
|
|
||||||
let { data: addrData} = await axios.get(`${ETHERMINT_REST_ENDPOINT}${generateEndpointAccount(senderAddress)}`)
|
|
||||||
|
|
||||||
const chain = {
|
|
||||||
chainId: 9000,
|
|
||||||
cosmosChainId: 'ethermint_9000-1',
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = {
|
|
||||||
accountAddress: addrData.account.base_account.address,
|
|
||||||
sequence: addrData.account.base_account.sequence,
|
|
||||||
accountNumber: addrData.account.base_account.account_number,
|
|
||||||
pubkey: addrData.account.base_account.pub_key.key,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fee = {
|
|
||||||
amount: '20',
|
|
||||||
denom: 'aphoton',
|
|
||||||
gas: '200000',
|
|
||||||
}
|
|
||||||
|
|
||||||
const memo = ''
|
|
||||||
|
|
||||||
const msg = createTxMsgRefillBond(chain, sender, fee, memo, params)
|
|
||||||
await signAndSendMessage(senderPrivateKey, chain, sender, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
const signAndSendMessage = async (senderPrivateKey: string, chain: Chain, sender: Sender, msg: any) => {
|
|
||||||
const eipMessageDomain: any = msg.eipToSign.domain;
|
|
||||||
|
|
||||||
// Sign transaction.
|
|
||||||
const signature = signTypedData({
|
|
||||||
data: {
|
|
||||||
types: msg.eipToSign.types as MessageTypes,
|
|
||||||
primaryType: msg.eipToSign.primaryType,
|
|
||||||
domain: eipMessageDomain as TypedMessageDomain,
|
|
||||||
message: msg.eipToSign.message as Record<string, unknown>
|
|
||||||
},
|
|
||||||
privateKey: Buffer.from(senderPrivateKey, 'hex'),
|
|
||||||
version: SignTypedDataVersion.V4
|
|
||||||
})
|
|
||||||
|
|
||||||
let extension = signatureToWeb3Extension(chain, sender, signature)
|
|
||||||
|
|
||||||
// Create the txRaw.
|
|
||||||
let rawTx = createTxRawEIP712(msg.legacyAmino.body, msg.legacyAmino.authInfo, extension)
|
|
||||||
|
|
||||||
const body = generatePostBodyBroadcast(rawTx)
|
|
||||||
|
|
||||||
// Broadcast transaction.
|
|
||||||
return axios.post(
|
|
||||||
`${ETHERMINT_REST_ENDPOINT}${generateEndpointBroadcast()}`,
|
|
||||||
JSON.parse(body)
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Check for successful broadcast.
|
|
||||||
}
|
}
|
||||||
|
47
src/registry-client.ts
Normal file
47
src/registry-client.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { generateEndpointAccount, generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry
|
||||||
|
*/
|
||||||
|
export class RegistryClient {
|
||||||
|
_endpoint: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New Client.
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} options
|
||||||
|
*/
|
||||||
|
constructor(endpoint: string) {
|
||||||
|
assert(endpoint);
|
||||||
|
|
||||||
|
this._endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Account.
|
||||||
|
*/
|
||||||
|
async getAccount(address: string) {
|
||||||
|
assert(address);
|
||||||
|
|
||||||
|
let { data } = await axios.get(`${this._endpoint}${generateEndpointAccount(address)}`)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit transaction.
|
||||||
|
*/
|
||||||
|
async submit(tx: string) {
|
||||||
|
assert(tx);
|
||||||
|
|
||||||
|
// Broadcast transaction.
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`${this._endpoint}${generateEndpointBroadcast()}`,
|
||||||
|
tx
|
||||||
|
)
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
17
src/testing/helper.ts
Normal file
17
src/testing/helper.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const DEFAULT_PRIVATE_KEY = '0451f0bd95c855d52e76cdc8dd06f29097b944bfef26d3455725157f9133f4e0';
|
||||||
|
const DEFAULT_ADDRESS = 'ethm19n3je0lhuk0w9kmkftsuw4etn8lmpu3jjfayeh'
|
||||||
|
|
||||||
|
export const getConfig = () => ({
|
||||||
|
mockServer: process.env.MOCK_SERVER || false,
|
||||||
|
chibaClonk: {
|
||||||
|
chainId: process.env.CHIBA_CLONK_CHAIN_ID || 'ethermint_9000-1',
|
||||||
|
privateKey: DEFAULT_PRIVATE_KEY,
|
||||||
|
accountAddress: DEFAULT_ADDRESS,
|
||||||
|
endpoint: process.env.CHIBA_CLONK_ENDPOINT || 'http://localhost:1317',
|
||||||
|
fee: {
|
||||||
|
amount: '20',
|
||||||
|
denom: 'aphoton',
|
||||||
|
gas: '200000',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
25
src/txbuilder.ts
Normal file
25
src/txbuilder.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import {
|
||||||
|
createTxRawEIP712,
|
||||||
|
signatureToWeb3Extension,
|
||||||
|
Chain,
|
||||||
|
Sender
|
||||||
|
} from '@tharsis/transactions'
|
||||||
|
|
||||||
|
import { Account } from './account';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a cosmos-sdk transaction.
|
||||||
|
*/
|
||||||
|
export const createTransaction = (message: any, account: Account, sender: Sender, chain: Chain) => {
|
||||||
|
assert(message);
|
||||||
|
assert(account);
|
||||||
|
|
||||||
|
// Sign transaction.
|
||||||
|
const signature = account.sign(message);
|
||||||
|
|
||||||
|
let extension = signatureToWeb3Extension(chain, sender, signature)
|
||||||
|
|
||||||
|
// Create the txRaw.
|
||||||
|
return createTxRawEIP712(message.legacyAmino.body, message.legacyAmino.authInfo, extension)
|
||||||
|
};
|
@ -42,12 +42,12 @@
|
|||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||||
|
|
||||||
/* Emit */
|
/* Emit */
|
||||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
@ -97,5 +97,11 @@
|
|||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
62
yarn.lock
62
yarn.lock
@ -324,6 +324,19 @@
|
|||||||
elliptic "^6.5.3"
|
elliptic "^6.5.3"
|
||||||
libsodium-wrappers "^0.7.6"
|
libsodium-wrappers "^0.7.6"
|
||||||
|
|
||||||
|
"@cosmjs/crypto@^0.28.1":
|
||||||
|
version "0.28.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.28.1.tgz#2c7ec4bbda6dd23eee7171e5897588203c5610f6"
|
||||||
|
integrity sha512-QLgP+xvd3X4vNU9PPnEGc1PI5qctgg1o6ANivqHgiJdX2bFolsqCqFQDs1rvGf8GWLJ2eGwXZPX1c/QK0bT9+A==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/encoding" "0.28.1"
|
||||||
|
"@cosmjs/math" "0.28.1"
|
||||||
|
"@cosmjs/utils" "0.28.1"
|
||||||
|
"@noble/hashes" "^1"
|
||||||
|
bn.js "^5.2.0"
|
||||||
|
elliptic "^6.5.3"
|
||||||
|
libsodium-wrappers "^0.7.6"
|
||||||
|
|
||||||
"@cosmjs/encoding@0.28.0":
|
"@cosmjs/encoding@0.28.0":
|
||||||
version "0.28.0"
|
version "0.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.28.0.tgz#e93f1ad0ee887074bb094e9c1c388bdb479c1abb"
|
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.28.0.tgz#e93f1ad0ee887074bb094e9c1c388bdb479c1abb"
|
||||||
@ -333,6 +346,15 @@
|
|||||||
bech32 "^1.1.4"
|
bech32 "^1.1.4"
|
||||||
readonly-date "^1.0.0"
|
readonly-date "^1.0.0"
|
||||||
|
|
||||||
|
"@cosmjs/encoding@0.28.1", "@cosmjs/encoding@^0.28.1":
|
||||||
|
version "0.28.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.28.1.tgz#e7214a29d73847c23e5ae28adeec081c3b1e0f92"
|
||||||
|
integrity sha512-FqKc+P5rBKq8hW2WHF6L8dmSJhy9mVBDhnJNCLUwyiKBywY9m4BZNTa0mPVQSXISx/c5DPbpJ5SChGL72qNgBw==
|
||||||
|
dependencies:
|
||||||
|
base64-js "^1.3.0"
|
||||||
|
bech32 "^1.1.4"
|
||||||
|
readonly-date "^1.0.0"
|
||||||
|
|
||||||
"@cosmjs/json-rpc@0.28.0":
|
"@cosmjs/json-rpc@0.28.0":
|
||||||
version "0.28.0"
|
version "0.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.28.0.tgz#cbb8ee4c47bb9d2fbd0f70395d3e715097962eb2"
|
resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.28.0.tgz#cbb8ee4c47bb9d2fbd0f70395d3e715097962eb2"
|
||||||
@ -348,6 +370,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
bn.js "^5.2.0"
|
bn.js "^5.2.0"
|
||||||
|
|
||||||
|
"@cosmjs/math@0.28.1":
|
||||||
|
version "0.28.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.28.1.tgz#3f2bbf14674f7a0f70a09413ba0bba140e77f1cc"
|
||||||
|
integrity sha512-rF5q4BSwNBo0kNBi8asaoHsRx/TchJ/P4IlRjXY8UGCfKCkSRQEID3ffgE8naXf+BDn5x4cSC8da3xy/aCZpAA==
|
||||||
|
dependencies:
|
||||||
|
bn.js "^5.2.0"
|
||||||
|
|
||||||
"@cosmjs/proto-signing@0.28.0", "@cosmjs/proto-signing@^0.28.0":
|
"@cosmjs/proto-signing@0.28.0", "@cosmjs/proto-signing@^0.28.0":
|
||||||
version "0.28.0"
|
version "0.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.28.0.tgz#d0e6880a6cb0115c78a5bca9cc89490e4b5e5e69"
|
resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.28.0.tgz#d0e6880a6cb0115c78a5bca9cc89490e4b5e5e69"
|
||||||
@ -415,6 +444,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.28.0.tgz#a2fefb68b7e2dddabf27f739a0f51578f7ebb4dc"
|
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.28.0.tgz#a2fefb68b7e2dddabf27f739a0f51578f7ebb4dc"
|
||||||
integrity sha512-1Um7h2a20ipbvEw0dzKPHL8qTbH5YY9ND0u5XxlVaCxaYDMTpzjjPiQD+Offxx/28afi8cuHWDbJc45dJXoCAg==
|
integrity sha512-1Um7h2a20ipbvEw0dzKPHL8qTbH5YY9ND0u5XxlVaCxaYDMTpzjjPiQD+Offxx/28afi8cuHWDbJc45dJXoCAg==
|
||||||
|
|
||||||
|
"@cosmjs/utils@0.28.1":
|
||||||
|
version "0.28.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.28.1.tgz#3c5043c6b4f92a2eba1aba63bed737b542b21662"
|
||||||
|
integrity sha512-PhdsifctdpMUXeWQjbQiHeOCOhWtK/OXdEG3E2PvvYxlmWHNu1faio+u2gZU6PPjL+qgqlAu92sybwsw/TRa+w==
|
||||||
|
|
||||||
"@cspotcode/source-map-consumer@0.8.0":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
||||||
@ -1180,6 +1214,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/is-url@^1.2.30":
|
||||||
|
version "1.2.30"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a"
|
||||||
|
integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw==
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
|
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
|
||||||
@ -1234,7 +1273,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17"
|
||||||
integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==
|
integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==
|
||||||
|
|
||||||
"@types/secp256k1@^4.0.1":
|
"@types/ripemd160@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/ripemd160/-/ripemd160-2.0.0.tgz#d33e49cf66edf4668828030d4aa80116bbf8ae81"
|
||||||
|
integrity sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/secp256k1@^4.0.1", "@types/secp256k1@^4.0.3":
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c"
|
resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c"
|
||||||
integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==
|
integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==
|
||||||
@ -2341,6 +2387,11 @@ is-typedarray@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||||
|
|
||||||
|
is-url@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||||
|
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
|
||||||
|
|
||||||
isexe@^2.0.0:
|
isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
@ -2798,6 +2849,11 @@ jest@^27.5.1:
|
|||||||
import-local "^3.0.2"
|
import-local "^3.0.2"
|
||||||
jest-cli "^27.5.1"
|
jest-cli "^27.5.1"
|
||||||
|
|
||||||
|
js-sha256@^0.9.0:
|
||||||
|
version "0.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
|
||||||
|
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
|
||||||
|
|
||||||
js-sha3@0.8.0:
|
js-sha3@0.8.0:
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
||||||
@ -3340,7 +3396,7 @@ ripemd160-min@0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62"
|
resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62"
|
||||||
integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==
|
integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==
|
||||||
|
|
||||||
ripemd160@^2.0.0, ripemd160@^2.0.1:
|
ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
|
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
|
||||||
integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
|
integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
|
||||||
@ -3382,7 +3438,7 @@ scrypt-js@3.0.1, scrypt-js@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
|
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
|
||||||
integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==
|
integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==
|
||||||
|
|
||||||
secp256k1@^4.0.1:
|
secp256k1@^4.0.1, secp256k1@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303"
|
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303"
|
||||||
integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==
|
integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==
|
||||||
|
Loading…
Reference in New Issue
Block a user