mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
Remove BCP's dependency on @cosmwasm/cosmwasm
This commit is contained in:
parent
4a350c2409
commit
1e8ed6fc10
@ -36,7 +36,6 @@
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmwasm/cosmwasm": "^0.8.0",
|
||||
"@cosmwasm/sdk38": "^0.8.0",
|
||||
"@iov/bcp": "^2.1.0",
|
||||
"@iov/crypto": "^2.1.0",
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Address, PostableBytes, PrehashType, SendTransaction, TokenTicker } from "@iov/bcp";
|
||||
import { PostableBytes, PrehashType } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
|
||||
const { toUtf8 } = Encoding;
|
||||
|
||||
@ -17,26 +17,8 @@ const defaultBankTokens: readonly BankToken[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const defaultErc20Tokens: readonly Erc20Token[] = [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "HASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "JADE",
|
||||
},
|
||||
];
|
||||
|
||||
describe("CosmWasmCodec", () => {
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultBankTokens, defaultErc20Tokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultBankTokens);
|
||||
|
||||
describe("isValidAddress", () => {
|
||||
it("accepts valid addresses", () => {
|
||||
@ -65,38 +47,6 @@ describe("CosmWasmCodec", () => {
|
||||
};
|
||||
expect(codec.bytesToSign(sendTxJson, nonce)).toEqual(expected);
|
||||
});
|
||||
|
||||
it("works for ERC20 send", () => {
|
||||
const bashSendTx: SendTransaction = {
|
||||
kind: "bcp/send",
|
||||
chainId: chainId,
|
||||
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq" as Address,
|
||||
recipient: "cosmos1dddd" as Address,
|
||||
memo: "My first ISA payment",
|
||||
amount: {
|
||||
fractionalDigits: 0,
|
||||
quantity: "345",
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
fee: {
|
||||
tokens: {
|
||||
fractionalDigits: 6,
|
||||
quantity: "2500",
|
||||
tokenTicker: "ATOM" as TokenTicker,
|
||||
},
|
||||
gasLimit: "100000",
|
||||
},
|
||||
};
|
||||
|
||||
const expected = {
|
||||
bytes: toUtf8(
|
||||
'{"account_number":"0","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"2500","denom":"uatom"}],"gas":"100000"},"memo":"My first ISA payment","msgs":[{"type":"wasm/execute","value":{"contract":"cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd","msg":{"transfer":{"amount":"345","recipient":"cosmos1dddd"}},"sender":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","sent_funds":[]}}],"sequence":"99"}',
|
||||
),
|
||||
prehashType: PrehashType.Sha256,
|
||||
};
|
||||
|
||||
expect(codec.bytesToSign(bashSendTx, nonce)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bytesToPost", () => {
|
||||
|
@ -20,25 +20,19 @@ import { pubkeyToAddress } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { parseSignedTx } from "./decode";
|
||||
import { buildSignedTx, buildUnsignedTx } from "./encode";
|
||||
import { BankToken, Erc20Token, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
import { BankToken, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
|
||||
export class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix: string;
|
||||
private readonly bankTokens: readonly BankToken[];
|
||||
private readonly erc20Tokens: readonly Erc20Token[];
|
||||
|
||||
public constructor(
|
||||
addressPrefix: string,
|
||||
bankTokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
) {
|
||||
public constructor(addressPrefix: string, bankTokens: readonly BankToken[]) {
|
||||
this.addressPrefix = addressPrefix;
|
||||
this.bankTokens = bankTokens;
|
||||
this.erc20Tokens = erc20Tokens;
|
||||
}
|
||||
|
||||
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
|
||||
const built = buildUnsignedTx(unsigned, this.bankTokens, this.erc20Tokens);
|
||||
const built = buildUnsignedTx(unsigned, this.bankTokens);
|
||||
|
||||
const signBytes = makeSignBytes(
|
||||
built.value.msg,
|
||||
@ -58,7 +52,7 @@ export class CosmWasmCodec implements TxCodec {
|
||||
// PostableBytes are JSON-encoded StdTx
|
||||
public bytesToPost(signed: SignedTransaction): PostableBytes {
|
||||
// TODO: change this as well (return StdTx, not AminoTx)?
|
||||
const built = buildSignedTx(signed, this.bankTokens, this.erc20Tokens);
|
||||
const built = buildSignedTx(signed, this.bankTokens);
|
||||
return marshalTx(built.value) as PostableBytes;
|
||||
}
|
||||
|
||||
@ -76,7 +70,7 @@ export class CosmWasmCodec implements TxCodec {
|
||||
throw new Error("Nonce is required");
|
||||
}
|
||||
const parsed = unmarshalTx(bytes);
|
||||
return parseSignedTx(parsed, chainId, nonce, this.bankTokens, this.erc20Tokens);
|
||||
return parseSignedTx(parsed, chainId, nonce, this.bankTokens);
|
||||
}
|
||||
|
||||
public identityToAddress(identity: Identity): Address {
|
||||
|
@ -69,7 +69,6 @@ const bob = {
|
||||
|
||||
describe("CosmWasmConnection", () => {
|
||||
const cosm = "COSM" as TokenTicker;
|
||||
const isa = "ISA" as TokenTicker;
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
|
||||
@ -104,26 +103,6 @@ describe("CosmWasmConnection", () => {
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "HASH",
|
||||
name: "Hash Token",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
name: "Isa Token",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "JADE",
|
||||
name: "Jade Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const atomConfig: TokenConfiguration = {
|
||||
@ -198,21 +177,6 @@ describe("CosmWasmConnection", () => {
|
||||
tokenName: "Fee Token",
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 5,
|
||||
tokenName: "Hash Token",
|
||||
tokenTicker: "HASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 0,
|
||||
tokenName: "Isa Token",
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 18,
|
||||
tokenName: "Jade Token",
|
||||
tokenTicker: "JADE" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Staking Token",
|
||||
@ -255,16 +219,6 @@ describe("CosmWasmConnection", () => {
|
||||
quantity: "1000000000",
|
||||
fractionalDigits: 6,
|
||||
},
|
||||
{
|
||||
tokenTicker: "HASH" as TokenTicker,
|
||||
quantity: "12812345",
|
||||
fractionalDigits: 5,
|
||||
},
|
||||
{
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
quantity: "42",
|
||||
fractionalDigits: 0,
|
||||
},
|
||||
{
|
||||
tokenTicker: "STAKE" as TokenTicker,
|
||||
quantity: "1000000000",
|
||||
@ -408,56 +362,6 @@ describe("CosmWasmConnection", () => {
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can get a recently posted ERC20 send transaction", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
|
||||
const senderIdentity = await profile.createIdentity(wallet.id, defaultChainId, bob.path);
|
||||
const senderAddress = connection.codec.identityToAddress(senderIdentity);
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
chainId: defaultChainId,
|
||||
sender: senderAddress,
|
||||
recipient: defaultRecipient,
|
||||
memo: "An ERC20 payment",
|
||||
amount: {
|
||||
quantity: "345",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: isa,
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: senderAddress });
|
||||
const signed = await profile.signTransaction(senderIdentity, unsigned, connection.codec, nonce);
|
||||
const postableBytes = connection.codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
const { transactionId } = response;
|
||||
await response.blockInfo.waitFor((info) => isBlockInfoSucceeded(info));
|
||||
|
||||
const getResponse = await connection.getTx(transactionId);
|
||||
expect(getResponse.transactionId).toEqual(transactionId);
|
||||
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
|
||||
assert(getResponse.log, "Log must be available");
|
||||
const [firstLog] = JSON.parse(getResponse.log);
|
||||
expect(firstLog.events.length).toEqual(2);
|
||||
|
||||
const { transaction, signatures } = getResponse;
|
||||
assert(isSendTransaction(transaction), "Expected send transaction");
|
||||
expect(transaction).toEqual(unsigned);
|
||||
expect(signatures.length).toEqual(1);
|
||||
expect(signatures[0]).toEqual({
|
||||
nonce: signed.signatures[0].nonce,
|
||||
pubkey: {
|
||||
algo: signed.signatures[0].pubkey.algo,
|
||||
data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data),
|
||||
},
|
||||
signature: Secp256k1.trimRecoveryByte(signed.signatures[0].signature),
|
||||
});
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can get an old transaction", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
|
||||
@ -604,124 +508,6 @@ describe("CosmWasmConnection", () => {
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can post an ERC20 transfer and search for the transaction", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
|
||||
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path);
|
||||
const senderAddress = connection.codec.identityToAddress(sender);
|
||||
|
||||
const recipient = makeRandomAddress();
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
chainId: defaultChainId,
|
||||
sender: senderAddress,
|
||||
recipient: recipient,
|
||||
memo: "My first payment",
|
||||
amount: {
|
||||
quantity: "75",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: senderAddress });
|
||||
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
|
||||
const postableBytes = connection.codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
const { transactionId } = response;
|
||||
const blockInfo = await response.blockInfo.waitFor((info) => !isBlockInfoPending(info));
|
||||
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
|
||||
|
||||
// search by id
|
||||
const byIdResults = await connection.searchTx({ id: transactionId });
|
||||
expect(byIdResults.length).toEqual(1);
|
||||
const byIdResult = byIdResults[0];
|
||||
expect(byIdResult.transactionId).toEqual(transactionId);
|
||||
assert(isConfirmedTransaction(byIdResult), "Expected transaction to succeed");
|
||||
assert(byIdResult.log, "Log must be available");
|
||||
const [firstByIdlog] = JSON.parse(byIdResult.log);
|
||||
expect(firstByIdlog.events.length).toEqual(2);
|
||||
expect(firstByIdlog.events[0].type).toEqual("message");
|
||||
expect(firstByIdlog.events[1].type).toEqual("wasm");
|
||||
// wasm event attributes added by contract
|
||||
expect(firstByIdlog.events[1].attributes).toContain({ key: "action", value: "transfer" });
|
||||
expect(firstByIdlog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
|
||||
expect(firstByIdlog.events[1].attributes).toContain({ key: "recipient", value: recipient });
|
||||
// wasm event attributes added wasmd
|
||||
expect(firstByIdlog.events[1].attributes).toContain({
|
||||
key: "contract_address",
|
||||
value: defaultConfig.erc20Tokens![1].contractAddress,
|
||||
});
|
||||
const byIdTransaction = byIdResult.transaction;
|
||||
assert(isSendTransaction(byIdTransaction), "Expected send transaction");
|
||||
expect(byIdTransaction).toEqual(unsigned);
|
||||
|
||||
// search by sender address
|
||||
const bySenderResults = await connection.searchTx({ sentFromOrTo: senderAddress });
|
||||
expect(bySenderResults).toBeTruthy();
|
||||
expect(bySenderResults.length).toBeGreaterThanOrEqual(1);
|
||||
const bySenderResult = bySenderResults[bySenderResults.length - 1];
|
||||
expect(bySenderResult.transactionId).toEqual(transactionId);
|
||||
assert(isConfirmedTransaction(bySenderResult), "Expected transaction to succeed");
|
||||
assert(bySenderResult.log, "Log must be available");
|
||||
const [firstBySenderLog] = JSON.parse(bySenderResult.log);
|
||||
expect(firstBySenderLog.events.length).toEqual(2);
|
||||
expect(firstBySenderLog.events[0].type).toEqual("message");
|
||||
expect(firstBySenderLog.events[1].type).toEqual("wasm");
|
||||
// wasm event attributes added by contract
|
||||
expect(firstBySenderLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
|
||||
expect(firstBySenderLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
|
||||
expect(firstBySenderLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
|
||||
// wasm event attributes added wasmd
|
||||
expect(firstBySenderLog.events[1].attributes).toContain({
|
||||
key: "contract_address",
|
||||
value: defaultConfig.erc20Tokens![1].contractAddress,
|
||||
});
|
||||
const bySenderTransaction = bySenderResult.transaction;
|
||||
assert(isSendTransaction(bySenderTransaction), "Expected send transaction");
|
||||
expect(bySenderTransaction).toEqual(unsigned);
|
||||
|
||||
// search by recipient address
|
||||
const byRecipientResults = await connection.searchTx({ sentFromOrTo: recipient });
|
||||
expect(byRecipientResults.length).toBeGreaterThanOrEqual(1);
|
||||
const byRecipientResult = byRecipientResults[byRecipientResults.length - 1];
|
||||
expect(byRecipientResult.transactionId).toEqual(transactionId);
|
||||
assert(isConfirmedTransaction(byRecipientResult), "Expected transaction to succeed");
|
||||
assert(byRecipientResult.log, "Log must be available");
|
||||
const [firstByRecipientLog] = JSON.parse(bySenderResult.log);
|
||||
expect(firstByRecipientLog.events.length).toEqual(2);
|
||||
expect(firstByRecipientLog.events[0].type).toEqual("message");
|
||||
expect(firstByRecipientLog.events[1].type).toEqual("wasm");
|
||||
// wasm event attributes added by contract
|
||||
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
|
||||
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
|
||||
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
|
||||
// wasm event attributes added wasmd
|
||||
expect(firstByRecipientLog.events[1].attributes).toContain({
|
||||
key: "contract_address",
|
||||
value: defaultConfig.erc20Tokens![1].contractAddress,
|
||||
});
|
||||
const byRecipeintTransaction = byRecipientResult.transaction;
|
||||
assert(isSendTransaction(byRecipeintTransaction), "Expected send transaction");
|
||||
expect(byRecipeintTransaction).toEqual(unsigned);
|
||||
|
||||
// search by height
|
||||
const heightResults = await connection.searchTx({ height: byIdResult.height });
|
||||
expect(heightResults.length).toEqual(1);
|
||||
const heightResult = heightResults[0];
|
||||
expect(heightResult.transactionId).toEqual(transactionId);
|
||||
assert(isConfirmedTransaction(heightResult), "Expected transaction to succeed");
|
||||
assert(heightResult.log, "Log must be available");
|
||||
const [firstHeightLog] = JSON.parse(heightResult.log);
|
||||
expect(firstHeightLog.events.length).toEqual(2);
|
||||
const heightTransaction = heightResult.transaction;
|
||||
assert(isSendTransaction(heightTransaction), "Expected send transaction");
|
||||
expect(heightTransaction).toEqual(unsigned);
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can search by minHeight and maxHeight", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
|
||||
|
@ -1,15 +1,15 @@
|
||||
import {
|
||||
CosmWasmClient,
|
||||
isMsgExecuteContract,
|
||||
isMsgInstantiateContract,
|
||||
isMsgStoreCode,
|
||||
} from "@cosmwasm/cosmwasm";
|
||||
import { findSequenceForSignedTx, IndexedTx, isMsgSend, isStdTx, SearchTxFilter } from "@cosmwasm/sdk38";
|
||||
CosmosClient,
|
||||
findSequenceForSignedTx,
|
||||
IndexedTx,
|
||||
isMsgSend,
|
||||
isStdTx,
|
||||
SearchTxFilter,
|
||||
} from "@cosmwasm/sdk38";
|
||||
import {
|
||||
Account,
|
||||
AccountQuery,
|
||||
AddressQuery,
|
||||
Amount,
|
||||
BlockchainConnection,
|
||||
BlockHeader,
|
||||
BlockId,
|
||||
@ -37,7 +37,6 @@ import {
|
||||
} from "@iov/bcp";
|
||||
import { Encoding, Uint53 } from "@iov/encoding";
|
||||
import { concat, DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
|
||||
import BN from "bn.js";
|
||||
import equal from "fast-deep-equal";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
import { Producer, Stream } from "xstream";
|
||||
@ -47,7 +46,7 @@ import { Caip5 } from "./caip5";
|
||||
import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { decodeAmount, decodePubkey, parseTxsResponseSigned, parseTxsResponseUnsigned } from "./decode";
|
||||
import { buildSignedTx } from "./encode";
|
||||
import { accountToNonce, BankToken, Erc20Token } from "./types";
|
||||
import { accountToNonce, BankToken } from "./types";
|
||||
|
||||
// poll every 0.5 seconds (block time 1s)
|
||||
const defaultPollInterval = 500;
|
||||
@ -55,8 +54,6 @@ const defaultPollInterval = 500;
|
||||
export interface TokenConfiguration {
|
||||
/** Supported tokens of the Cosmos SDK bank module */
|
||||
readonly bankTokens: ReadonlyArray<BankToken & { readonly name: string }>;
|
||||
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
|
||||
readonly erc20Tokens?: ReadonlyArray<Erc20Token & { readonly name: string }>;
|
||||
}
|
||||
|
||||
function isDefined<X>(value: X | undefined): value is X {
|
||||
@ -92,43 +89,40 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
addressPrefix: string,
|
||||
tokens: TokenConfiguration,
|
||||
): Promise<CosmWasmConnection> {
|
||||
const cosmWasmClient = new CosmWasmClient(url);
|
||||
const chainData = await this.initialize(cosmWasmClient);
|
||||
return new CosmWasmConnection(cosmWasmClient, chainData, addressPrefix, tokens);
|
||||
const cosmosClient = new CosmosClient(url);
|
||||
const chainData = await this.initialize(cosmosClient);
|
||||
return new CosmWasmConnection(cosmosClient, chainData, addressPrefix, tokens);
|
||||
}
|
||||
|
||||
private static async initialize(cosmWasmClient: CosmWasmClient): Promise<ChainId> {
|
||||
const rawChainId = await cosmWasmClient.getChainId();
|
||||
private static async initialize(cosmosClient: CosmosClient): Promise<ChainId> {
|
||||
const rawChainId = await cosmosClient.getChainId();
|
||||
return Caip5.encode(rawChainId);
|
||||
}
|
||||
|
||||
public readonly chainId: ChainId;
|
||||
public readonly codec: TxCodec;
|
||||
|
||||
private readonly cosmWasmClient: CosmWasmClient;
|
||||
private readonly cosmosClient: CosmosClient;
|
||||
private readonly addressPrefix: string;
|
||||
private readonly bankTokens: readonly BankToken[];
|
||||
private readonly erc20Tokens: readonly Erc20Token[];
|
||||
|
||||
// these are derived from arguments (cached for use in multiple functions)
|
||||
private readonly feeToken: BankToken | undefined;
|
||||
private readonly supportedTokens: readonly Token[];
|
||||
|
||||
private constructor(
|
||||
cosmWasmClient: CosmWasmClient,
|
||||
cosmosClient: CosmosClient,
|
||||
chainId: ChainId,
|
||||
addressPrefix: string,
|
||||
tokens: TokenConfiguration,
|
||||
) {
|
||||
this.cosmWasmClient = cosmWasmClient;
|
||||
this.cosmosClient = cosmosClient;
|
||||
this.chainId = chainId;
|
||||
this.codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens, tokens.erc20Tokens);
|
||||
this.codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens);
|
||||
this.addressPrefix = addressPrefix;
|
||||
this.bankTokens = tokens.bankTokens;
|
||||
this.feeToken = this.bankTokens.find(() => true);
|
||||
const erc20Tokens = tokens.erc20Tokens || [];
|
||||
this.erc20Tokens = erc20Tokens;
|
||||
this.supportedTokens = [...tokens.bankTokens, ...erc20Tokens]
|
||||
this.supportedTokens = tokens.bankTokens
|
||||
.map((info) => ({
|
||||
tokenTicker: info.ticker as TokenTicker,
|
||||
tokenName: info.name,
|
||||
@ -142,7 +136,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
public async height(): Promise<number> {
|
||||
return this.cosmWasmClient.getHeight();
|
||||
return this.cosmosClient.getHeight();
|
||||
}
|
||||
|
||||
public async getToken(searchTicker: TokenTicker): Promise<Token | undefined> {
|
||||
@ -158,40 +152,24 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
* context and network available, which we might use to implement the API in an async way.
|
||||
*/
|
||||
public async identifier(signed: SignedTransaction): Promise<TransactionId> {
|
||||
const tx = buildSignedTx(signed, this.bankTokens, this.erc20Tokens);
|
||||
const id = await this.cosmWasmClient.getIdentifier(tx);
|
||||
const tx = buildSignedTx(signed, this.bankTokens);
|
||||
const id = await this.cosmosClient.getIdentifier(tx);
|
||||
return id as TransactionId;
|
||||
}
|
||||
|
||||
public async getAccount(query: AccountQuery): Promise<Account | undefined> {
|
||||
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
|
||||
const bankAccount = await this.cosmWasmClient.getAccount(address);
|
||||
const bankAccount = await this.cosmosClient.getAccount(address);
|
||||
|
||||
const supportedBankCoins = (bankAccount?.balance || []).filter(({ denom }) =>
|
||||
this.bankTokens.find((token) => token.denom === denom),
|
||||
);
|
||||
const erc20Amounts = await Promise.all(
|
||||
this.erc20Tokens.map(
|
||||
async (erc20): Promise<Amount> => {
|
||||
const queryMsg = { balance: { address: address } };
|
||||
const response = await this.cosmWasmClient.queryContractSmart(erc20.contractAddress, queryMsg);
|
||||
const normalizedBalance = new BN(response.balance).toString();
|
||||
return {
|
||||
fractionalDigits: erc20.fractionalDigits,
|
||||
quantity: normalizedBalance,
|
||||
tokenTicker: erc20.ticker as TokenTicker,
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
const nonZeroErc20Amounts = erc20Amounts.filter((amount) => amount.quantity !== "0");
|
||||
|
||||
if (!bankAccount && nonZeroErc20Amounts.length === 0) {
|
||||
if (!bankAccount) {
|
||||
return undefined;
|
||||
} else {
|
||||
const balance = [
|
||||
...supportedBankCoins.map((coin) => decodeAmount(this.bankTokens, coin)),
|
||||
...nonZeroErc20Amounts,
|
||||
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
|
||||
const pubkey = bankAccount?.pubkey ? decodePubkey(bankAccount.pubkey) : undefined;
|
||||
return {
|
||||
@ -234,7 +212,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
|
||||
public async getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce> {
|
||||
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
|
||||
const { accountNumber, sequence } = await this.cosmWasmClient.getNonce(address);
|
||||
const { accountNumber, sequence } = await this.cosmosClient.getNonce(address);
|
||||
return accountToNonce(accountNumber, sequence);
|
||||
}
|
||||
|
||||
@ -249,7 +227,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
public async getBlockHeader(height: number): Promise<BlockHeader> {
|
||||
const { id, header, txs } = await this.cosmWasmClient.getBlock(height);
|
||||
const { id, header, txs } = await this.cosmosClient.getBlock(height);
|
||||
return {
|
||||
id: id as BlockId,
|
||||
height: header.height,
|
||||
@ -265,7 +243,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
public async getTx(
|
||||
id: TransactionId,
|
||||
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
|
||||
const results = await this.cosmWasmClient.searchTx({ id: id });
|
||||
const results = await this.cosmosClient.searchTx({ id: id });
|
||||
switch (results.length) {
|
||||
case 0:
|
||||
throw new Error("Transaction does not exist");
|
||||
@ -279,7 +257,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
public async postTx(tx: PostableBytes): Promise<PostTxResponse> {
|
||||
const txAsJson = JSON.parse(Encoding.fromUtf8(tx));
|
||||
if (!isStdTx(txAsJson)) throw new Error("Postable bytes must contain a JSON encoded StdTx");
|
||||
const { transactionHash, rawLog } = await this.cosmWasmClient.postTx(txAsJson);
|
||||
const { transactionHash, rawLog } = await this.cosmosClient.postTx(txAsJson);
|
||||
const transactionId = transactionHash as TransactionId;
|
||||
const firstEvent: BlockInfo = { state: TransactionState.Pending };
|
||||
let blockInfoInterval: NodeJS.Timeout;
|
||||
@ -340,36 +318,12 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
|
||||
let txs: readonly IndexedTx[];
|
||||
if (id) {
|
||||
txs = await this.cosmWasmClient.searchTx({ id: id }, filter);
|
||||
txs = await this.cosmosClient.searchTx({ id: id }, filter);
|
||||
} else if (height) {
|
||||
txs = await this.cosmWasmClient.searchTx({ height: height }, filter);
|
||||
txs = await this.cosmosClient.searchTx({ height: height }, filter);
|
||||
} else if (sentFromOrTo) {
|
||||
const pendingRequests = new Array<Promise<readonly IndexedTx[]>>();
|
||||
pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
|
||||
for (const contract of this.erc20Tokens.map((token) => token.contractAddress)) {
|
||||
const searchBySender = [
|
||||
{
|
||||
key: "wasm.contract_address",
|
||||
value: contract,
|
||||
},
|
||||
{
|
||||
key: "wasm.sender",
|
||||
value: sentFromOrTo,
|
||||
},
|
||||
];
|
||||
const searchByRecipient = [
|
||||
{
|
||||
key: "wasm.contract_address",
|
||||
value: contract,
|
||||
},
|
||||
{
|
||||
key: "wasm.recipient",
|
||||
value: sentFromOrTo,
|
||||
},
|
||||
];
|
||||
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchBySender }, filter));
|
||||
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchByRecipient }, filter));
|
||||
}
|
||||
pendingRequests.push(this.cosmosClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
|
||||
const responses = await Promise.all(pendingRequests);
|
||||
const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []);
|
||||
txs = deduplicate(allResults, (a, b) => a.hash.localeCompare(b.hash)).sort(compareByHeightAndHash);
|
||||
@ -463,13 +417,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
private parseAndPopulateTxResponseUnsigned(
|
||||
response: IndexedTx,
|
||||
): ConfirmedTransaction<UnsignedTransaction> | FailedTransaction {
|
||||
return parseTxsResponseUnsigned(
|
||||
this.chainId,
|
||||
response.height,
|
||||
response,
|
||||
this.bankTokens,
|
||||
this.erc20Tokens,
|
||||
);
|
||||
return parseTxsResponseUnsigned(this.chainId, response.height, response, this.bankTokens);
|
||||
}
|
||||
|
||||
private async parseAndPopulateTxResponseSigned(
|
||||
@ -481,17 +429,11 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
let senderAddress: string;
|
||||
if (isMsgSend(firstMsg)) {
|
||||
senderAddress = firstMsg.value.from_address;
|
||||
} else if (
|
||||
isMsgStoreCode(firstMsg) ||
|
||||
isMsgInstantiateContract(firstMsg) ||
|
||||
isMsgExecuteContract(firstMsg)
|
||||
) {
|
||||
senderAddress = firstMsg.value.sender;
|
||||
} else {
|
||||
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
|
||||
}
|
||||
|
||||
const { accountNumber, sequence: currentSequence } = await this.cosmWasmClient.getNonce(senderAddress);
|
||||
const { accountNumber, sequence: currentSequence } = await this.cosmosClient.getNonce(senderAddress);
|
||||
const sequenceForTx = await findSequenceForSignedTx(
|
||||
response.tx,
|
||||
Caip5.decode(this.chainId),
|
||||
@ -502,14 +444,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
|
||||
const nonce = accountToNonce(accountNumber, sequenceForTx);
|
||||
|
||||
return parseTxsResponseSigned(
|
||||
this.chainId,
|
||||
response.height,
|
||||
nonce,
|
||||
response,
|
||||
this.bankTokens,
|
||||
this.erc20Tokens,
|
||||
);
|
||||
return parseTxsResponseSigned(this.chainId, response.height, nonce, response, this.bankTokens);
|
||||
}
|
||||
|
||||
private waitForTransaction(
|
||||
|
@ -12,7 +12,7 @@ export function createCosmWasmConnector(
|
||||
tokenConfig: TokenConfiguration,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmWasmConnection> {
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokenConfig.bankTokens, tokenConfig.erc20Tokens);
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokenConfig.bankTokens);
|
||||
return {
|
||||
establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokenConfig),
|
||||
codec: codec,
|
||||
|
@ -1,9 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { MsgExecuteContract } from "@cosmwasm/cosmwasm";
|
||||
import { Coin, IndexedTx, Msg, PubKey, StdSignature, StdTx } from "@cosmwasm/sdk38";
|
||||
import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp";
|
||||
import { Coin, IndexedTx, Msg, PubKey, StdSignature } from "@cosmwasm/sdk38";
|
||||
import { Address, Algorithm, SendTransaction, TokenTicker } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { assert } from "@iov/utils";
|
||||
|
||||
import {
|
||||
decodeAmount,
|
||||
@ -19,7 +17,7 @@ import {
|
||||
} from "./decode";
|
||||
import * as testdata from "./testdata.spec";
|
||||
import cosmoshub from "./testdata/cosmoshub.json";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
|
||||
const { fromBase64, fromHex } = Encoding;
|
||||
|
||||
@ -65,23 +63,6 @@ describe("decode", () => {
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
const defaultErc20Tokens: Erc20Token[] = [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "HASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "JADE",
|
||||
},
|
||||
];
|
||||
|
||||
describe("decodePubkey", () => {
|
||||
it("works for secp256k1", () => {
|
||||
@ -159,40 +140,7 @@ describe("decode", () => {
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens, defaultErc20Tokens)).toEqual(
|
||||
defaultSendTransaction,
|
||||
);
|
||||
});
|
||||
|
||||
it("works for ERC20 send transaction", () => {
|
||||
const msg: MsgExecuteContract = {
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
|
||||
contract: defaultErc20Tokens[0].contractAddress,
|
||||
msg: {
|
||||
transfer: {
|
||||
amount: "887878484",
|
||||
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
|
||||
},
|
||||
},
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const transaction = parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens, defaultErc20Tokens);
|
||||
assert(isSendTransaction(transaction));
|
||||
expect(transaction).toEqual({
|
||||
kind: "bcp/send",
|
||||
chainId: testdata.chainId,
|
||||
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address,
|
||||
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address,
|
||||
amount: {
|
||||
quantity: "887878484",
|
||||
tokenTicker: "HASH" as TokenTicker,
|
||||
fractionalDigits: 5,
|
||||
},
|
||||
memo: defaultMemo,
|
||||
});
|
||||
expect(parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens)).toEqual(defaultSendTransaction);
|
||||
});
|
||||
});
|
||||
|
||||
@ -213,69 +161,17 @@ describe("decode", () => {
|
||||
|
||||
describe("parseUnsignedTx", () => {
|
||||
it("works for bank send transaction", () => {
|
||||
expect(
|
||||
parseUnsignedTx(cosmoshub.tx.value, testdata.chainId, defaultTokens, defaultErc20Tokens),
|
||||
).toEqual(testdata.sendTxJson);
|
||||
});
|
||||
|
||||
it("works for ERC20 send transaction", () => {
|
||||
const msg: MsgExecuteContract = {
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
|
||||
contract: defaultErc20Tokens[0].contractAddress,
|
||||
msg: {
|
||||
transfer: {
|
||||
amount: "887878484",
|
||||
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
|
||||
},
|
||||
},
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const tx: StdTx = {
|
||||
msg: [msg],
|
||||
memo: defaultMemo,
|
||||
fee: {
|
||||
amount: [
|
||||
{
|
||||
denom: "uatom",
|
||||
amount: "5000",
|
||||
},
|
||||
],
|
||||
gas: "200000",
|
||||
},
|
||||
signatures: [],
|
||||
};
|
||||
const unsigned = parseUnsignedTx(tx, testdata.chainId, defaultTokens, defaultErc20Tokens);
|
||||
assert(isSendTransaction(unsigned));
|
||||
expect(unsigned).toEqual({
|
||||
kind: "bcp/send",
|
||||
chainId: testdata.chainId,
|
||||
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address,
|
||||
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address,
|
||||
amount: {
|
||||
quantity: "887878484",
|
||||
tokenTicker: "HASH" as TokenTicker,
|
||||
fractionalDigits: 5,
|
||||
},
|
||||
memo: defaultMemo,
|
||||
fee: defaultFee,
|
||||
});
|
||||
expect(parseUnsignedTx(cosmoshub.tx.value, testdata.chainId, defaultTokens)).toEqual(
|
||||
testdata.sendTxJson,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseSignedTx", () => {
|
||||
it("works", () => {
|
||||
expect(
|
||||
parseSignedTx(
|
||||
cosmoshub.tx.value,
|
||||
testdata.chainId,
|
||||
testdata.nonce,
|
||||
defaultTokens,
|
||||
defaultErc20Tokens,
|
||||
),
|
||||
).toEqual(testdata.signedTxJson);
|
||||
expect(parseSignedTx(cosmoshub.tx.value, testdata.chainId, testdata.nonce, defaultTokens)).toEqual(
|
||||
testdata.signedTxJson,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -298,15 +194,9 @@ describe("decode", () => {
|
||||
transactionId: testdata.txId,
|
||||
log: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
};
|
||||
expect(
|
||||
parseTxsResponseUnsigned(
|
||||
testdata.chainId,
|
||||
currentHeight,
|
||||
txsResponse,
|
||||
defaultTokens,
|
||||
defaultErc20Tokens,
|
||||
),
|
||||
).toEqual(expected);
|
||||
expect(parseTxsResponseUnsigned(testdata.chainId, currentHeight, txsResponse, defaultTokens)).toEqual(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -330,14 +220,7 @@ describe("decode", () => {
|
||||
log: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
};
|
||||
expect(
|
||||
parseTxsResponseSigned(
|
||||
testdata.chainId,
|
||||
currentHeight,
|
||||
testdata.nonce,
|
||||
txsResponse,
|
||||
defaultTokens,
|
||||
defaultErc20Tokens,
|
||||
),
|
||||
parseTxsResponseSigned(testdata.chainId, currentHeight, testdata.nonce, txsResponse, defaultTokens),
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { isMsgExecuteContract } from "@cosmwasm/cosmwasm";
|
||||
import {
|
||||
Coin,
|
||||
IndexedTx,
|
||||
@ -31,9 +30,8 @@ import {
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Decimal, Encoding } from "@iov/encoding";
|
||||
import BN from "bn.js";
|
||||
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -89,7 +87,6 @@ export function parseMsg(
|
||||
memo: string | undefined,
|
||||
chainId: ChainId,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): UnsignedTransaction {
|
||||
if (isMsgSend(msg)) {
|
||||
if (msg.value.amount.length !== 1) {
|
||||
@ -104,34 +101,6 @@ export function parseMsg(
|
||||
memo: memo,
|
||||
};
|
||||
return send;
|
||||
} else if (isMsgExecuteContract(msg)) {
|
||||
const matchingTokenContract = erc20Tokens.find((t) => t.contractAddress === msg.value.contract);
|
||||
if (!matchingTokenContract) {
|
||||
return {
|
||||
chainId: chainId,
|
||||
kind: "bcp/unknown",
|
||||
};
|
||||
}
|
||||
|
||||
const recipient: string | undefined = msg.value.msg.transfer?.recipient;
|
||||
if (!recipient) throw new Error("Could not read recipient");
|
||||
|
||||
const amount: string | undefined = msg.value.msg.transfer?.amount;
|
||||
if (!amount) throw new Error("Could not read recipient");
|
||||
|
||||
const send: SendTransaction = {
|
||||
kind: "bcp/send",
|
||||
chainId: chainId,
|
||||
sender: msg.value.sender as Address,
|
||||
recipient: recipient as Address,
|
||||
amount: {
|
||||
quantity: new BN(amount).toString(),
|
||||
fractionalDigits: matchingTokenContract.fractionalDigits,
|
||||
tokenTicker: matchingTokenContract.ticker as TokenTicker,
|
||||
},
|
||||
memo: memo,
|
||||
};
|
||||
return send;
|
||||
} else {
|
||||
// Unknown transaction type
|
||||
const unknown = {
|
||||
@ -156,7 +125,6 @@ export function parseUnsignedTx(
|
||||
txValue: StdTx,
|
||||
chainId: ChainId,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): UnsignedTransaction {
|
||||
if (!isStdTx(txValue)) {
|
||||
throw new Error("Only StdTx is supported");
|
||||
@ -165,7 +133,7 @@ export function parseUnsignedTx(
|
||||
throw new Error("Only single-message transactions currently supported");
|
||||
}
|
||||
|
||||
const msg = parseMsg(txValue.msg[0], txValue.memo, chainId, tokens, erc20Tokens);
|
||||
const msg = parseMsg(txValue.msg[0], txValue.memo, chainId, tokens);
|
||||
const fee = parseFee(txValue.fee, tokens);
|
||||
|
||||
return {
|
||||
@ -180,11 +148,10 @@ export function parseSignedTx(
|
||||
chainId: ChainId,
|
||||
nonce: Nonce,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): SignedTransaction {
|
||||
const [primarySignature] = txValue.signatures.map((signature) => decodeFullSignature(signature, nonce));
|
||||
return {
|
||||
transaction: parseUnsignedTx(txValue, chainId, tokens, erc20Tokens),
|
||||
transaction: parseUnsignedTx(txValue, chainId, tokens),
|
||||
signatures: [primarySignature],
|
||||
};
|
||||
}
|
||||
@ -194,10 +161,9 @@ export function parseTxsResponseUnsigned(
|
||||
currentHeight: number,
|
||||
response: IndexedTx,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedTransaction<UnsignedTransaction> {
|
||||
return {
|
||||
transaction: parseUnsignedTx(response.tx.value, chainId, tokens, erc20Tokens),
|
||||
transaction: parseUnsignedTx(response.tx.value, chainId, tokens),
|
||||
height: response.height,
|
||||
confirmations: currentHeight - response.height + 1,
|
||||
transactionId: response.hash as TransactionId,
|
||||
@ -211,10 +177,9 @@ export function parseTxsResponseSigned(
|
||||
nonce: Nonce,
|
||||
response: IndexedTx,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
|
||||
return {
|
||||
...parseSignedTx(response.tx.value, chainId, nonce, tokens, erc20Tokens),
|
||||
...parseSignedTx(response.tx.value, chainId, nonce, tokens),
|
||||
height: response.height,
|
||||
confirmations: currentHeight - response.height + 1,
|
||||
transactionId: response.hash as TransactionId,
|
||||
|
@ -20,9 +20,8 @@ import {
|
||||
encodeFullSignature,
|
||||
encodePubkey,
|
||||
toBankCoin,
|
||||
toErc20Amount,
|
||||
} from "./encode";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -49,23 +48,6 @@ describe("encode", () => {
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
const defaultErc20Tokens: Erc20Token[] = [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "HASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "JADE",
|
||||
},
|
||||
];
|
||||
|
||||
describe("encodePubkey", () => {
|
||||
it("works for compressed public key", () => {
|
||||
@ -76,37 +58,6 @@ describe("encode", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("toErc20Amount", () => {
|
||||
const [ash, bash] = defaultErc20Tokens;
|
||||
|
||||
it("encodes an amount", () => {
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
};
|
||||
expect(toErc20Amount(amount, bash)).toEqual("789");
|
||||
});
|
||||
|
||||
it("throws on ticker mismatch", () => {
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
};
|
||||
expect(() => toErc20Amount(amount, ash)).toThrowError(/ticker mismatch/i);
|
||||
});
|
||||
|
||||
it("throws on ticker mismatch", () => {
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 2,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
};
|
||||
expect(() => toErc20Amount(amount, bash)).toThrowError(/fractional digits mismatch/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toBankCoin", () => {
|
||||
it("encodes an amount", () => {
|
||||
expect(toBankCoin(defaultAmount, defaultTokens)).toEqual({
|
||||
@ -287,56 +238,6 @@ describe("encode", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("works for ERC20 send", () => {
|
||||
const bashSendTx: SendTransaction = {
|
||||
kind: "bcp/send",
|
||||
chainId: defaultChainId,
|
||||
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq" as Address,
|
||||
recipient: "cosmos1dddd" as Address,
|
||||
memo: defaultMemo,
|
||||
amount: {
|
||||
fractionalDigits: 0,
|
||||
quantity: "345",
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
fee: {
|
||||
tokens: {
|
||||
fractionalDigits: 6,
|
||||
quantity: "3333",
|
||||
tokenTicker: "ATOM" as TokenTicker,
|
||||
},
|
||||
gasLimit: "234000",
|
||||
},
|
||||
};
|
||||
expect(buildUnsignedTx(bashSendTx, defaultTokens, defaultErc20Tokens)).toEqual({
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
{
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq",
|
||||
contract: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
msg: {
|
||||
transfer: {
|
||||
recipient: "cosmos1dddd",
|
||||
amount: "345",
|
||||
},
|
||||
},
|
||||
sent_funds: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
fee: {
|
||||
amount: [{ denom: "uatom", amount: "3333" }],
|
||||
gas: "234000",
|
||||
},
|
||||
signatures: [],
|
||||
memo: defaultMemo,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSignedTx", () => {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
|
||||
const { toBase64 } = Encoding;
|
||||
|
||||
@ -41,14 +41,6 @@ export function encodePubkey(pubkey: PubkeyBundle): PubKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string {
|
||||
if (amount.tokenTicker !== erc20Token.ticker) throw new Error("Ticker mismatch between amount and token");
|
||||
if (amount.fractionalDigits !== erc20Token.fractionalDigits) {
|
||||
throw new Error("Fractional digits mismatch between amount and token");
|
||||
}
|
||||
return amount.quantity;
|
||||
}
|
||||
|
||||
export function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin {
|
||||
const match = tokens.find((token) => token.ticker === amount.tokenTicker);
|
||||
if (!match) throw Error(`unknown ticker: ${amount.tokenTicker}`);
|
||||
@ -88,17 +80,12 @@ export function encodeFullSignature(fullSignature: FullSignature): StdSignature
|
||||
}
|
||||
}
|
||||
|
||||
export function buildUnsignedTx(
|
||||
tx: UnsignedTransaction,
|
||||
bankTokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
): CosmosSdkTx {
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
|
||||
if (!isSendTransaction(tx)) {
|
||||
throw new Error("Received transaction of unsupported kind");
|
||||
}
|
||||
|
||||
const matchingBankToken = bankTokens.find((t) => t.ticker === tx.amount.tokenTicker);
|
||||
const matchingErc20Token = erc20Tokens.find((t) => t.ticker === tx.amount.tokenTicker);
|
||||
|
||||
if (!tx.fee) throw new Error("Transaction fee must be set");
|
||||
|
||||
@ -121,42 +108,13 @@ export function buildUnsignedTx(
|
||||
fee: encodeFee(tx.fee, bankTokens),
|
||||
},
|
||||
};
|
||||
} else if (matchingErc20Token) {
|
||||
return {
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
{
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: tx.sender,
|
||||
contract: matchingErc20Token.contractAddress,
|
||||
msg: {
|
||||
transfer: {
|
||||
amount: toErc20Amount(tx.amount, matchingErc20Token),
|
||||
recipient: tx.recipient,
|
||||
},
|
||||
},
|
||||
sent_funds: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
memo: tx.memo || "",
|
||||
signatures: [],
|
||||
fee: encodeFee(tx.fee, bankTokens),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw new Error("Cannot encode this type of transaction");
|
||||
}
|
||||
}
|
||||
|
||||
export function buildSignedTx(
|
||||
tx: SignedTransaction,
|
||||
bankTokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
): CosmosSdkTx {
|
||||
const built = buildUnsignedTx(tx.transaction, bankTokens, erc20Tokens);
|
||||
export function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
|
||||
const built = buildUnsignedTx(tx.transaction, bankTokens);
|
||||
return {
|
||||
...built,
|
||||
value: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
export { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
export { createCosmWasmConnector } from "./cosmwasmconnector";
|
||||
export { BankToken, Erc20Token } from "./types";
|
||||
export { BankToken } from "./types";
|
||||
|
@ -15,21 +15,6 @@ export interface BankToken {
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
export interface Erc20Token {
|
||||
readonly contractAddress: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
* The number of fractional digits the token supports.
|
||||
*
|
||||
* A quantity is expressed as atomic units. 10^fractionalDigits of those
|
||||
* atomic units make up 1 token.
|
||||
*
|
||||
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
|
||||
* the last 18 digits are the fractional part and the rest the wole part.
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const maxAcct = 1 << 23;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
|
5
packages/bcp/types/cosmwasmcodec.d.ts
vendored
5
packages/bcp/types/cosmwasmcodec.d.ts
vendored
@ -10,12 +10,11 @@ import {
|
||||
TxCodec,
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
export declare class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix;
|
||||
private readonly bankTokens;
|
||||
private readonly erc20Tokens;
|
||||
constructor(addressPrefix: string, bankTokens: readonly BankToken[], erc20Tokens?: readonly Erc20Token[]);
|
||||
constructor(addressPrefix: string, bankTokens: readonly BankToken[]);
|
||||
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
|
||||
bytesToPost(signed: SignedTransaction): PostableBytes;
|
||||
identifier(_signed: SignedTransaction): TransactionId;
|
||||
|
11
packages/bcp/types/cosmwasmconnection.d.ts
vendored
11
packages/bcp/types/cosmwasmconnection.d.ts
vendored
@ -22,7 +22,7 @@ import {
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Stream } from "xstream";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
export interface TokenConfiguration {
|
||||
/** Supported tokens of the Cosmos SDK bank module */
|
||||
readonly bankTokens: ReadonlyArray<
|
||||
@ -30,12 +30,6 @@ export interface TokenConfiguration {
|
||||
readonly name: string;
|
||||
}
|
||||
>;
|
||||
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
|
||||
readonly erc20Tokens?: ReadonlyArray<
|
||||
Erc20Token & {
|
||||
readonly name: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
export declare class CosmWasmConnection implements BlockchainConnection {
|
||||
static establish(
|
||||
@ -46,10 +40,9 @@ export declare class CosmWasmConnection implements BlockchainConnection {
|
||||
private static initialize;
|
||||
readonly chainId: ChainId;
|
||||
readonly codec: TxCodec;
|
||||
private readonly cosmWasmClient;
|
||||
private readonly cosmosClient;
|
||||
private readonly addressPrefix;
|
||||
private readonly bankTokens;
|
||||
private readonly erc20Tokens;
|
||||
private readonly feeToken;
|
||||
private readonly supportedTokens;
|
||||
private constructor();
|
||||
|
7
packages/bcp/types/decode.d.ts
vendored
7
packages/bcp/types/decode.d.ts
vendored
@ -13,7 +13,7 @@ import {
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Decimal } from "@iov/encoding";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
export declare function decodePubkey(pubkey: PubKey): PubkeyBundle;
|
||||
export declare function decodeSignature(signature: string): SignatureBytes;
|
||||
export declare function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature;
|
||||
@ -24,28 +24,24 @@ export declare function parseMsg(
|
||||
memo: string | undefined,
|
||||
chainId: ChainId,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): UnsignedTransaction;
|
||||
export declare function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee;
|
||||
export declare function parseUnsignedTx(
|
||||
txValue: StdTx,
|
||||
chainId: ChainId,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): UnsignedTransaction;
|
||||
export declare function parseSignedTx(
|
||||
txValue: StdTx,
|
||||
chainId: ChainId,
|
||||
nonce: Nonce,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): SignedTransaction;
|
||||
export declare function parseTxsResponseUnsigned(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
response: IndexedTx,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedTransaction<UnsignedTransaction>;
|
||||
export declare function parseTxsResponseSigned(
|
||||
chainId: ChainId,
|
||||
@ -53,5 +49,4 @@ export declare function parseTxsResponseSigned(
|
||||
nonce: Nonce,
|
||||
response: IndexedTx,
|
||||
tokens: readonly BankToken[],
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction>;
|
||||
|
10
packages/bcp/types/encode.d.ts
vendored
10
packages/bcp/types/encode.d.ts
vendored
@ -1,18 +1,12 @@
|
||||
import { Coin, CosmosSdkTx, PubKey, StdFee, StdSignature } from "@cosmwasm/sdk38";
|
||||
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
import { BankToken } from "./types";
|
||||
export declare function encodePubkey(pubkey: PubkeyBundle): PubKey;
|
||||
export declare function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string;
|
||||
export declare function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin;
|
||||
export declare function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee;
|
||||
export declare function encodeFullSignature(fullSignature: FullSignature): StdSignature;
|
||||
export declare function buildUnsignedTx(
|
||||
tx: UnsignedTransaction,
|
||||
bankTokens: readonly BankToken[],
|
||||
erc20Tokens?: readonly Erc20Token[],
|
||||
): CosmosSdkTx;
|
||||
export declare function buildSignedTx(
|
||||
tx: SignedTransaction,
|
||||
bankTokens: readonly BankToken[],
|
||||
erc20Tokens?: readonly Erc20Token[],
|
||||
): CosmosSdkTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx;
|
||||
|
2
packages/bcp/types/index.d.ts
vendored
2
packages/bcp/types/index.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
export { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
export { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
export { createCosmWasmConnector } from "./cosmwasmconnector";
|
||||
export { BankToken, Erc20Token } from "./types";
|
||||
export { BankToken } from "./types";
|
||||
|
14
packages/bcp/types/types.d.ts
vendored
14
packages/bcp/types/types.d.ts
vendored
@ -13,20 +13,6 @@ export interface BankToken {
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
export interface Erc20Token {
|
||||
readonly contractAddress: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
* The number of fractional digits the token supports.
|
||||
*
|
||||
* A quantity is expressed as atomic units. 10^fractionalDigits of those
|
||||
* atomic units make up 1 token.
|
||||
*
|
||||
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
|
||||
* the last 18 digits are the fractional part and the rest the wole part.
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
export declare function accountToNonce(accountNumber: number, sequence: number): Nonce;
|
||||
export declare function nonceToAccountNumber(nonce: Nonce): number;
|
||||
export declare function nonceToSequence(nonce: Nonce): number;
|
||||
|
@ -23,12 +23,4 @@ export const developmentTokenConfig: TokenConfiguration = {
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
name: "Isa Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -30,18 +30,10 @@ const defaultConfig: TokenConfiguration = {
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "ISA",
|
||||
name: "Isa Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
const defaultAddressPrefix = "cosmos";
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
const codec = new CosmWasmCodec(defaultAddressPrefix, defaultConfig.bankTokens, defaultConfig.erc20Tokens);
|
||||
const codec = new CosmWasmCodec(defaultAddressPrefix, defaultConfig.bankTokens);
|
||||
|
||||
function makeRandomAddress(): Address {
|
||||
return Bech32.encode(defaultAddressPrefix, Random.getBytes(20)) as Address;
|
||||
@ -100,33 +92,6 @@ describe("Faucet", () => {
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can send ERC20 token", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
|
||||
const { profile, holder } = await makeProfile();
|
||||
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||
const recipient = makeRandomAddress();
|
||||
await faucet.send({
|
||||
amount: {
|
||||
quantity: "7",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
sender: holder,
|
||||
recipient: recipient,
|
||||
});
|
||||
const account = await connection.getAccount({ address: recipient });
|
||||
assert(account);
|
||||
expect(account.balance).toEqual([
|
||||
{
|
||||
quantity: "7",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "ISA" as TokenTicker,
|
||||
},
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe("refill", () => {
|
||||
@ -143,18 +108,13 @@ describe("Faucet", () => {
|
||||
tokenTicker: "COSM",
|
||||
fractionalDigits: 6,
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
tokenTicker: "ISA",
|
||||
fractionalDigits: 0,
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
tokenTicker: "STAKE",
|
||||
fractionalDigits: 6,
|
||||
}),
|
||||
]);
|
||||
expect(Number.parseInt(distributorBalance[0].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80);
|
||||
expect(Number.parseInt(distributorBalance[2].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
@ -206,7 +166,7 @@ describe("Faucet", () => {
|
||||
const { profile } = await makeProfile();
|
||||
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||
const tickers = await faucet.loadTokenTickers();
|
||||
expect(tickers).toEqual(["COSM", "ISA", "STAKE"]);
|
||||
expect(tickers).toEqual(["COSM", "STAKE"]);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
|
@ -58,9 +58,7 @@ export class TokenManager {
|
||||
}
|
||||
|
||||
private getFractionalDigits(ticker: TokenTicker): number {
|
||||
const match = [...this.config.bankTokens, ...(this.config.erc20Tokens || [])].find(
|
||||
(token) => token.ticker === ticker,
|
||||
);
|
||||
const match = this.config.bankTokens.find((token) => token.ticker === ticker);
|
||||
if (!match) throw new Error(`No token found for ticker symbol: ${ticker}`);
|
||||
return match.fractionalDigits;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user