Feat/x core api (#3698)

* fix: lint

* fix: ada lint

* fix: ext html lazy js build

* feat: algo core chain

* feat: core chain Aptos

* feat: core chain BTC

* feat: core chain BCH

* feat: core chain DOGE LTC

* feat: core chain CFX

* feat: core chain cosmos

* fix: lint

* feat: core chain DOT FIL

* fix: rename types and enums

* feat: core chain kaspa

* feat: core chain near

* feat: core chain sol

* feat: core chain stc

* feat: core chain sui

* feat: core chain xrp

* feat: core chain xmr

* feat: core chain tron

* feat: core chain nexa

* feat: core chain evm

* fix: lint

* fix: lint
This commit is contained in:
morizon
2023-10-24 11:27:15 +08:00
committed by GitHub
parent 9b429c0367
commit cdafb9293d
233 changed files with 28473 additions and 442 deletions

View File

@@ -95,13 +95,13 @@ const tsRules = {
};
module.exports = {
ignorePatterns: [
'*.wasm.bin',
'packages/components/src/Icon/*',
'packages/desktop/public/static/js-sdk/*',
// 临时忽略以下目录的检查,迭代后会逐步开启
'packages/blockchain-libs',
'packages/kit/src/store',
'packages/kit/src/utils/localAuthentication',
'packages/core',
'packages/engine',
'packages/kit-bg',
'packages/shared',

View File

@@ -55,4 +55,6 @@ jobs:
NODE_OPTIONS: '--max_old_space_size=4096'
run: |
yarn --mode=skip-build && yarn patch-package
yarn lint
yarn lint && yarn test

1
.gitignore vendored
View File

@@ -7,7 +7,6 @@ __generated__
.idea/
.vscode/
.chat/
packages/core/src/chains/_x/
dist
build-electron

View File

@@ -0,0 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable camelcase */
declare module '@mymonero/mymonero-app-bridge/MyMoneroLibAppBridgeClass' {
export default class MyMoneroLibAppBridgeClass {
constructor(props: any);
async__send_funds: (...args: any[]) => void;
}
}

View File

@@ -0,0 +1,15 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable camelcase */
declare module '@mymonero/mymonero-keyimage-cache' {
export const Lazy_KeyImage: (
mutable_keyImagesByCacheKey: any,
tx_pub_key: string,
out_index: string,
public_address: string,
view_key__private: string,
spend_key__public: string,
spend_key__private: string,
coreBridge_instance: any,
) => string;
export const Lazy_KeyImageCacheForWalletWith: (address: string) => any;
}

View File

@@ -0,0 +1,91 @@
declare module '@mymonero/mymonero-lws-client' {
interface MoneroTransaction {
coinbase: boolean;
hash: string;
height: number;
id: number;
mempool: boolean;
mixin: number;
timestamp: string;
total_received: string;
total_sent: string;
unlock_time: number;
spent_outputs?: SpentOutout[];
fee?: string;
}
interface SpentOutout {
amount: string;
key_image: string;
mixin: number;
out_index: number;
tx_pub_key: string;
spend_key_images: string[];
index: number;
}
interface MoneroAddressInfo {
total_received: string;
total_sent: string;
locked_funds: string;
scanned_height: number;
start_height: number;
scanned_block_height: number;
blockchain_height: number;
transaction_height: number;
spent_outputs: SpentOutout[];
}
interface AmountOut {
amount: string;
outputs: SpentOutout[];
}
interface Props {
httpClient?: any;
url?: string;
api_key?: string;
appName?: string;
appVersion?: string;
}
export default class LWSClient {
constructor(props: Props);
login: (
viewKey: string,
address: string,
createAccount?: boolean,
) => Promise<{ isNewAddress: boolean }>;
getAddressTxs: (
viewKey: string,
address: string,
) => Promise<{
transactions: MoneroTransaction[];
blockchain_height: number;
}>;
getAddressInfo: (
viewKey: string,
address: string,
) => Promise<MoneroAddressInfo>;
unspentOutputs: (
viewKey: string,
address: string,
) => Promise<{
amount: string;
outputs: SpentOutout[];
per_byte_fee: number;
fee_mask: number;
fork_version: number;
}>;
randomOutputs: (
numberOfOutputs: number,
) => Promise<{ amount_outs: AmountOut[] }>;
}
export { MoneroAddressInfo, SpentOutout, MoneroTransaction };
}

215
@types/tronweb.d.ts vendored Normal file
View File

@@ -0,0 +1,215 @@
/* eslint-disable camelcase */
type ITokenContract = {
name: () => { call: () => Promise<{ _name: string } | string> };
symbol: () => { call: () => Promise<{ _symbol: string } | string> };
decimals: () => { call: () => Promise<{ _decimals: number } | number> };
balanceOf: (string) => { call: () => Promise<number> };
allowance: (
string,
string,
) => {
call: () => Promise<{ _hex: string } | { remaining: { _hex: string } }>;
};
totalSupply: () => { call: () => Promise<{ _hex: number }> };
};
type IAccountResources = {
freeNetUsed?: number;
freeNetLimit?: number;
NetUsed?: number;
NetLimit?: number;
EnergyUsed?: number;
EnergyLimit?: number;
};
type ISendTrxCall = {
parameter: {
value: {
amount: number;
owner_address: string;
to_address: string;
};
};
type: 'TransferContract';
};
type ITriggerSmartContractCall = {
parameter: {
value: {
data: string;
owner_address: string;
contract_address: string;
call_value?: number;
};
};
type: 'TriggerSmartContract';
};
type IFreezeBalanceV2ContractCall = {
parameter: {
value: {
frozen_balance: number;
resource: 'BANDWIDTH' | 'ENERGY';
};
};
type: 'FreezeBalanceV2Contract';
};
type IUnfreezeBalanceV2ContractCall = {
parameter: {
value: {
unfreeze_balance: number;
resource: 'BANDWIDTH' | 'ENERGY';
};
};
type: 'UnfreezeBalanceV2Contract';
};
type IDelegateResourceContractCall = {
parameter: {
value: {
balance: number;
receiver_address: string;
lock: boolean;
resource: 'BANDWIDTH' | 'ENERGY';
};
};
type: 'DelegateResourceContract';
};
type IUnDelegateResourceContractCall = {
parameter: {
value: {
balance: number;
receiver_address: string;
resource: 'BANDWIDTH' | 'ENERGY';
};
};
type: 'UnDelegateResourceContract';
};
type IWithdrawBalanceContractCall = {
parameter: {
value: {
owner_address: string;
};
};
type: 'WithdrawBalanceContract';
};
type IWithdrawExpireUnfreezeContractCall = {
type: 'WithdrawExpireUnfreezeContract';
};
type IUnsignedTransaction = {
txID: string;
raw_data: {
contract: Array<
| ISendTrxCall
| ITriggerSmartContractCall
| IFreezeBalanceV2ContractCall
| IUnfreezeBalanceV2ContractCall
| IDelegateResourceContractCall
| IUnDelegateResourceContractCall
| IWithdrawBalanceContractCall
| IWithdrawExpireUnfreezeContractCall
>;
ref_block_bytes: string;
ref_block_hash: string;
expiration: number;
timestamp: number;
fee_limit?: number;
};
raw_data_hex: string;
};
type ISignedTransaction = IUnsignedTransaction & {
signature: string[];
};
type ITransactionWithResult = IUnsignedTransaction & {
ret: [{ contractRet?: string }];
};
type ITransactionInfo = {
id: string;
fee: number;
blockNumber: number;
blockTimeStamp: number;
contractResult: number[];
contract_address: string;
internal_transactions: {
callValueInfo: { callValue: number }[];
caller_address: string;
hash: string;
note: string;
transferTo_address: string;
}[];
};
declare module 'tronweb' {
export class TronWeb {
constructor(e: any);
setAddress: (address: string) => void;
contract: () => {
at: (address: string) => Promise<ITokenContract>;
};
fullNode: {
request: (string, any?, string?) => Promise<any>;
};
trx: {
getAccount: (string) => Promise<{ address: string }>;
getAccountResources: (string) => Promise<IAccountResources>;
getBalance: (string) => Promise<number>;
getChainParameters: () => Promise<Array<{ key: string; value: any }>>;
getConfirmedTransaction: (string) => Promise<ITransactionWithResult>;
sendRawTransaction: (
any,
) => Promise<{ code?: string; message?: string; result?: boolean }>;
getTransaction: (string) => Promise<ITransactionWithResult>;
getTransactionInfo: (string) => Promise<ITransactionInfo>;
getNodeInfo: (
callback?: import('@onekeyfe/onekey-tron-provider/dist/types').Callback,
) => Promise<any>;
};
transactionBuilder: {
triggerSmartContract: (
string, // contract address
string, // function
any, // options
any, // parameters to call the function
string, // from address
) => Promise<{
result: { result: boolean };
transaction: IUnsignedTransaction;
}>;
estimateEnergy: (
string, // contract address
string, // function
any, // options
any, // parameters to call the function
string, // from address
) => Promise<{
result: { result: boolean };
energy_required: number;
}>;
sendTrx: (string, number, string) => Promise<IUnsignedTransaction>;
sendToken: (to, amount, tokenID, from) => Promise<IUnsignedTransaction>;
};
address: {
toHex: (string) => string;
};
static isAddress: (string) => boolean;
static address: {
fromHex: (string) => string;
};
}
export type Transaction = IUnsignedTransaction;
export type SignedTransaction = ISignedTransaction;
export default TronWeb;
}

View File

@@ -28,12 +28,18 @@ function doTaskInFolder({ folder }) {
return;
}
$('body script').each((idx, ele) => {
$('html head script').each((idx, ele) => {
const $ele = $(ele);
const src = $ele.attr('src');
srcList.push(src);
$ele.remove();
if (
!src.includes('preload-html-head.js') &&
!src.includes('react-render-tracker')
) {
srcList.push(src);
$ele.remove();
}
});
$body.append(
$(`

View File

@@ -24,7 +24,8 @@
"build:icon": "yarn workspace @onekeyhq/components build:icons",
"_lint": "sh -c 'npx eslint . --ext .ts,.tsx --fix --cache --cache-location \"$(yarn config get cacheFolder)\"'",
"lint:project": "yarn _lint",
"lint": "ultra lint:project"
"lint": "ultra lint:project",
"test": "yarn jest"
},
"dependencies": {
"@onekeyfe/cross-inpage-provider-core": "1.1.38",
@@ -75,6 +76,7 @@
"@tamagui/babel-plugin": "1.71.0",
"@types/chrome": "^0.0.248",
"@types/elliptic": "^6.4.16",
"@types/ethjs-util": "^0.1.2",
"@types/jest": "^29.5.6",
"@types/lodash": "^4.14.200",
"@types/memoizee": "^0.4.10",

View File

@@ -12,7 +12,7 @@ import type {
} from './fixtures/coreTestsFixtures';
import type { CoreChainApiBase } from '../src/base/CoreChainApiBase';
import type {
AddressEncodings,
EAddressEncodings,
ICoreApiGetAddressItem,
ICoreApiNetworkInfo,
ICoreApiSignAccount,
@@ -132,7 +132,7 @@ async function expectGetAddressFromHdOk({
hdAccounts: IPrepareCoreChainTestsFixturesOptions['hdAccounts'];
hdAccountTemplate: string;
hdCredential: ICoreTestsHdCredential;
addressEncoding?: AddressEncodings;
addressEncoding?: EAddressEncodings;
}) {
const indexes = range(0, hdAccounts.length);
const addresses = await coreApi.getAddressesFromHd({

View File

@@ -11,15 +11,41 @@
"author": "",
"license": "SEE https://github.com/OneKeyHQ/app-monorepo/blob/onekey/LICENSE.md",
"dependencies": {
"@noble/secp256k1": "^2.0.0",
"@conflux-dev/conflux-address-js": "^1.3.16",
"@glif/filecoin-address": "^2.0.43",
"@kaspa/core-lib": "^1.6.5",
"@mymonero/mymonero-app-bridge": "^3.0.0",
"@mymonero/mymonero-keyimage-cache": "^3.0.0",
"@noble/secp256k1": "1.7.1",
"@onekeyfe/cardano-coin-selection": "1.0.0",
"@onekeyfe/cardano-coin-selection-asmjs": "1.1.0",
"@solana/web3.js": "^1.87.2",
"@starcoin/starcoin": "^2.3.7",
"@zondax/izari-filecoin": "^1.2.0",
"algosdk": "^2.6.0",
"asmcrypto.js": "2.3.2",
"base32-decode": "^1.0.0",
"bchaddrjs": "^0.5.2",
"bip32": "^4.0.0",
"bip39": "^3.1.0",
"bitcoinforkjs": "git+https://github.com/OneKeyHQ/bitcoinjs-lib.git#feat/remove-npm-lock",
"bitcoinjs-lib": "^6.1.5",
"bitcoinjs-message": "2.2.0",
"bs58check": "^3.0.1",
"cardano-crypto.js": "^6.1.2",
"cosmjs-types": "^0.8.0",
"ecpair": "^2.1.0",
"elliptic": "^6.5.4",
"jsrsasign": "^10.8.6"
"js-conflux-sdk": "^2.1.12",
"jsrsasign": "^10.8.6",
"near-api-js": "^2.1.4",
"ripple-keypairs": "^1.3.1",
"tronweb": "^5.3.1",
"xrpl": "^2.13.0"
},
"devDependencies": {
"@types/bchaddrjs": "^0.4.2",
"@types/bs58": "^4.0.3",
"@types/jsrsasign": "^10.5.11"
}
}

View File

@@ -16,10 +16,10 @@ import {
getXprvString,
sdk,
} from './sdkAda';
import { NetworkId } from './types';
import { EAdaNetworkId } from './types';
import type { IAdaBaseAddressInfo, IAdaStakingAddressInfo } from './sdkAda';
import type { IAdaUTXO, IEncodedTxADA } from './types';
import type { IAdaUTXO, IEncodedTxAda } from './types';
import type { ISigner } from '../../base/ChainSigner';
import type {
ICoreApiGetAddressItem,
@@ -97,7 +97,7 @@ export default class CoreChainSoftware extends CoreChainApiBase {
): Promise<ISignedTxPro> {
// throw new Error('Method not implemented.');
const { unsignedTx, account } = payload;
const encodedTx = unsignedTx.encodedTx as IEncodedTxADA;
const encodedTx = unsignedTx.encodedTx as IEncodedTxAda;
const signer = await this.baseGetSingleSigner({
payload,
curve,
@@ -195,7 +195,7 @@ export default class CoreChainSoftware extends CoreChainApiBase {
const addressInfos = batchGetShelleyAddressByRootKey(
encodeKey.rootKey,
[index],
NetworkId.MAINNET,
EAdaNetworkId.MAINNET,
);
const { baseAddress, stakingAddress } = addressInfos[0];
@@ -229,7 +229,7 @@ export default class CoreChainSoftware extends CoreChainApiBase {
bufferUtils.toBuffer(entropy),
password,
indexes,
NetworkId.MAINNET,
EAdaNetworkId.MAINNET,
);
const addresses = addressInfos.map((info) => {

View File

@@ -15,13 +15,13 @@ import {
// @ts-expect-error
} from 'cardano-crypto.js';
import { NetworkId } from '../types';
import { EAdaNetworkId } from '../types';
import { HARDENED_THRESHOLD } from './constants';
import type { Address, BIP32Path } from '../types';
import type { IAdaAddress, IAdaBIP32Path } from '../types';
export const encodeAddress = (address: Buffer): Address => {
export const encodeAddress = (address: Buffer): IAdaAddress => {
const addressType = getAddressType(address);
if (addressType === AddressTypes.BOOTSTRAP) {
return base58.encode(address);
@@ -33,7 +33,7 @@ export const encodeAddress = (address: Buffer): Address => {
[AddressTypes.REWARD]: 'stake',
};
const isTestnet =
getShelleyAddressNetworkId(address) === NetworkId.TESTNET_OR_PREPROD;
getShelleyAddressNetworkId(address) === EAdaNetworkId.TESTNET_OR_PREPROD;
const addressPrefix = `${addressPrefixes[addressType]}${
isTestnet ? '_test' : ''
}`;
@@ -48,13 +48,13 @@ export const xpub2ChainCode = (xpub: Buffer) => xpub.slice(32, 64);
const xpub2blake2b224Hash = (xpub: Buffer) =>
getPubKeyBlake2b224Hash(xpub2pub(xpub));
export const isShelleyPath = (path: BIP32Path) =>
export const isShelleyPath = (path: IAdaBIP32Path) =>
path[0] - HARDENED_THRESHOLD === 1852;
export const stakingAddressFromXpub = (
stakeXpub: Buffer,
networkId: NetworkId,
): Address => {
networkId: EAdaNetworkId,
): IAdaAddress => {
const addrBuffer: Buffer = packRewardAddress(
xpub2blake2b224Hash(stakeXpub),
networkId,
@@ -65,8 +65,8 @@ export const stakingAddressFromXpub = (
export const baseAddressFromXpub = (
spendXpub: Buffer,
stakeXpub: Buffer,
networkId: NetworkId,
): Address => {
networkId: EAdaNetworkId,
): IAdaAddress => {
const addrBuffer = packBaseAddress(
xpub2blake2b224Hash(spendXpub),
xpub2blake2b224Hash(stakeXpub),

View File

@@ -9,9 +9,9 @@ import { mnemonicFromEntropy } from '@onekeyhq/core/src/secret';
import { DERIVATION_SCHEME, HARDENED_THRESHOLD } from './constants';
import type { BIP32Path } from '../types';
import type { IAdaBIP32Path } from '../types';
export function toBip32StringPath(derivationPath: BIP32Path) {
export function toBip32StringPath(derivationPath: IAdaBIP32Path) {
return `m/${derivationPath
.map(
(item) =>

View File

@@ -14,4 +14,8 @@
// stakingPath: `${dbAccount.path.slice(0, -3)}2/0`,
// },
// });
export default {};
export function getChangeAddress() {
throw new Error(
'ADA getChangeAddress not implemented in core, move it to upper layer',
);
}

View File

@@ -8,9 +8,9 @@ import { baseAddressFromXpub, stakingAddressFromXpub } from './addresses';
import { getRootKey, toBip32StringPath } from './bip32';
import { DERIVATION_SCHEME, HARDENED_THRESHOLD } from './constants';
import type { BIP32Path, NetworkId } from '../types';
import type { EAdaNetworkId, IAdaBIP32Path } from '../types';
const shelleyPath = (account: number): BIP32Path => [
const shelleyPath = (account: number): IAdaBIP32Path => [
HARDENED_THRESHOLD + 1852,
HARDENED_THRESHOLD + 1815,
HARDENED_THRESHOLD + account,
@@ -18,7 +18,7 @@ const shelleyPath = (account: number): BIP32Path => [
0,
];
const shelleyStakeAccountPath = (account: number): BIP32Path => [
const shelleyStakeAccountPath = (account: number): IAdaBIP32Path => [
HARDENED_THRESHOLD + 1852,
HARDENED_THRESHOLD + 1815,
HARDENED_THRESHOLD + account,
@@ -26,7 +26,7 @@ const shelleyStakeAccountPath = (account: number): BIP32Path => [
0,
];
export const derivePath = (paths: BIP32Path, rootKey: Buffer) =>
export const derivePath = (paths: IAdaBIP32Path, rootKey: Buffer) =>
paths.reduce(
(prev, path) => derivePrivate(prev, path, DERIVATION_SCHEME),
rootKey,
@@ -39,7 +39,7 @@ export const derivePath = (paths: BIP32Path, rootKey: Buffer) =>
* @returns hex
*/
export const deriveAccountXpub = (
paths: BIP32Path,
paths: IAdaBIP32Path,
rootKey: Buffer,
): string => {
const accountKey = derivePath(paths, rootKey);
@@ -52,19 +52,19 @@ export const deriveAccountXpub = (
* @param rootKey privateKey
* @returns Buffer
*/
export const deriveXpub = (paths: BIP32Path, rootKey: Buffer): Buffer => {
export const deriveXpub = (paths: IAdaBIP32Path, rootKey: Buffer): Buffer => {
const deriveSecret = derivePath(paths, rootKey);
return toPublic(deriveSecret.slice(0, 64));
};
export type IAdaStakingAddressInfo = {
path: BIP32Path;
path: IAdaBIP32Path;
address: string;
};
export function ShelleyStakingAccountProvider(
accountIndex: number,
rootKey: Buffer,
networkId: NetworkId,
networkId: EAdaNetworkId,
): IAdaStakingAddressInfo {
const pathStake = shelleyStakeAccountPath(accountIndex);
const stakeXpub = deriveXpub(pathStake, rootKey);
@@ -83,7 +83,7 @@ export type IAdaBaseAddressInfo = {
export function ShelleyBaseAddressProvider(
accountIndex: number,
rootKey: Buffer,
networkId: NetworkId,
networkId: EAdaNetworkId,
): IAdaBaseAddressInfo {
const pathSpend = shelleyPath(accountIndex);
const spendXpub = deriveXpub(pathSpend, rootKey);
@@ -103,7 +103,7 @@ export function ShelleyBaseAddressProvider(
export const batchGetShelleyAddressByRootKey = (
rootKey: Buffer,
indexes: number[],
networkId: NetworkId,
networkId: EAdaNetworkId,
) =>
indexes.map((accountIndex) => ({
baseAddress: ShelleyBaseAddressProvider(accountIndex, rootKey, networkId),
@@ -118,7 +118,7 @@ export const batchGetShelleyAddresses = async (
entropy: Buffer,
password: string,
indexes: number[],
networkId: NetworkId,
networkId: EAdaNetworkId,
) => {
const rootKey = await getRootKey(password, entropy);
return batchGetShelleyAddressByRootKey(rootKey, indexes, networkId);

View File

@@ -1,47 +1,19 @@
import type { PROTO } from '@onekeyfe/hd-core';
export type BIP32Path = number[];
export const enum NetworkId {
export const enum EAdaNetworkId {
MAINNET = 1,
TESTNET_OR_PREPROD = 0,
}
export type Address = string & { __typeAddress: any };
export type IAdaBIP32Path = number[];
// export type IAdaAccount = {
// 'stake_address': string;
// 'active': boolean;
// 'active_epoch': number;
// 'controlled_amount': string;
// 'rewards_sum': string;
// 'withdrawals_sum': string;
// 'reserves_sum': string;
// 'treasury_sum': string;
// 'withdrawable_amount': string;
// 'pool_id': string;
// };
export type IAdaAddress = string & { __typeAddress: any };
export type IAdaAmount = {
unit: string;
quantity: string;
};
// export type IAdaAddress = {
// 'address': string;
// 'amount': IAdaAmount[];
// 'stake_address': string;
// 'type': 'shelley';
// 'script': false;
// };
// export type IAdaAddressDetail = {
// address: string;
// received_sum?: IAdaAmount[];
// sent_sum?: IAdaAmount[];
// tx_count: number;
// };
export type IAdaUTXO = {
path: string;
address: string;
@@ -51,38 +23,7 @@ export type IAdaUTXO = {
amount: IAdaAmount[];
};
// export type IAdaOutputs = {
// address: string;
// amount: string;
// assets: [];
// };
// export type IAdaTransaction = {
// 'hash': string;
// 'block': string;
// 'block_height': number;
// 'block_time': number;
// 'slot': number;
// 'index': number;
// 'output_amount': IAdaAmount[];
// 'fees': string;
// 'deposit': string;
// 'size': number;
// 'invalid_before': string | null;
// 'invalid_hereafter': string | null;
// 'utxo_count': number;
// 'withdrawal_count': number;
// 'mir_cert_count': number;
// 'delegation_count': number;
// 'stake_cert_count': number;
// 'pool_update_count': number;
// 'pool_retire_count': number;
// 'asset_mint_or_burn_count': number;
// 'redeemer_count': number;
// 'valid_contract': boolean;
// };
type IEncodeInput = {
export type IAdaEncodeInput = {
address: string;
amount: IAdaAmount[];
block: string;
@@ -92,21 +33,21 @@ type IEncodeInput = {
tx_index: number;
};
export type IEncodeOutput = {
export type IAdaEncodeOutput = {
address: string;
amount: string;
assets: IAdaAmount[];
isChange?: boolean;
};
type ITxInfo = {
export type IAdaTxInfo = {
body: string;
hash: string;
size: number;
rawTxHex?: string;
};
export type IChangeAddress = {
export type IAdaChangeAddress = {
address: string;
addressParameters: {
path: string;
@@ -115,15 +56,15 @@ export type IChangeAddress = {
};
};
export type IEncodedTxADA = {
inputs: IEncodeInput[];
outputs: IEncodeOutput[];
export type IEncodedTxAda = {
inputs: IAdaEncodeInput[];
outputs: IAdaEncodeOutput[];
fee: string;
totalSpent: string;
totalFeeInNative: string;
// transferInfo: ITransferInfo; // TODO
tx: ITxInfo;
changeAddress: IChangeAddress;
tx: IAdaTxInfo;
changeAddress: IAdaChangeAddress;
signOnly?: boolean;
};
@@ -193,3 +134,59 @@ export type IEncodedTxADA = {
// fingerprint: string;
// quantity: string;
// };
// export type IAdaAccount = {
// 'stake_address': string;
// 'active': boolean;
// 'active_epoch': number;
// 'controlled_amount': string;
// 'rewards_sum': string;
// 'withdrawals_sum': string;
// 'reserves_sum': string;
// 'treasury_sum': string;
// 'withdrawable_amount': string;
// 'pool_id': string;
// };
// export type IAdaOutputs = {
// address: string;
// amount: string;
// assets: [];
// };
// export type IAdaTransaction = {
// 'hash': string;
// 'block': string;
// 'block_height': number;
// 'block_time': number;
// 'slot': number;
// 'index': number;
// 'output_amount': IAdaAmount[];
// 'fees': string;
// 'deposit': string;
// 'size': number;
// 'invalid_before': string | null;
// 'invalid_hereafter': string | null;
// 'utxo_count': number;
// 'withdrawal_count': number;
// 'mir_cert_count': number;
// 'delegation_count': number;
// 'stake_cert_count': number;
// 'pool_update_count': number;
// 'pool_retire_count': number;
// 'asset_mint_or_burn_count': number;
// 'redeemer_count': number;
// 'valid_contract': boolean;
// };
// export type IAdaAddress = {
// 'address': string;
// 'amount': IAdaAmount[];
// 'stake_address': string;
// 'type': 'shelley';
// 'script': false;
// };
// export type IAdaAddressDetail = {
// address: string;
// received_sum?: IAdaAmount[];
// sent_sum?: IAdaAmount[];
// tx_count: number;
// };

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,105 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'algo',
chainId: '4160',
networkId: 'algo--4160',
networkImpl: 'algo',
isTestnet: false,
},
hdAccountTemplate: "m/44'/283'/0'/0'/$$INDEX$$'",
hdAccounts: [
{
address: 'MDHU6RJPZUTSGZGMA3GRW52G3AITTDYRNC7POVQQZHH5ARU6EEXPNHO5FI',
path: "m/44'/283'/0'/0'/0'",
publicKey:
'60cf4f452fcd272364cc06cd1b7746d811398f1168bef75610c9cfd0469e212e',
privateKeyRaw:
'509ce94feb7f24a0f5b359f3435fdd3f1e9d38c47732a41b3503b68d01e8aaed',
},
],
txSamples: [
{
encodedTx:
'iKNmZWXNA+iiZnbOAfRQPKNnZW6sbWFpbm5ldC12MS4womdoxCDAYcTY/B293tLXYEvkVo4/bQQZh6w3veS2ILWrOSSK36Jsds4B9FQko3JjdsQgYM9PRS/NJyNkzAbNG3dG2BE5jxFovvdWEMnP0EaeIS6jc25kxCBgz09FL80nI2TMBs0bd0bYETmPEWi+91YQyc/QRp4hLqR0eXBlo3BheQ==',
signedTx: {
'txid': 'RXWEB2KZVNQDSIJUU34KWBPEIUTVUPFT3DQ7NMMFXZJIOXN2NE3A',
'rawTx':
'gqNzaWfEQO2iFonQmKSoDnysbfOmcZA/CAlKBk7KP7QC2AhXwWvXxlk9Vz/G0tN2J8ghvuPL/oBJEKIHdTgdjGEQ9tUglQOjdHhuiKNmZWXNA+iiZnbOAfRQPKNnZW6sbWFpbm5ldC12MS4womdoxCDAYcTY/B293tLXYEvkVo4/bQQZh6w3veS2ILWrOSSK36Jsds4B9FQko3JjdsQgYM9PRS/NJyNkzAbNG3dG2BE5jxFovvdWEMnP0EaeIS6jc25kxCBgz09FL80nI2TMBs0bd0bYETmPEWi+91YQyc/QRp4hLqR0eXBlo3BheQ==',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/algo/CoreChainSoftware.test.ts
describe('ALGO Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ALGO Core tests signTransaction 1`] = `
{
"rawTx": "gqNzaWfEQO2iFonQmKSoDnysbfOmcZA/CAlKBk7KP7QC2AhXwWvXxlk9Vz/G0tN2J8ghvuPL/oBJEKIHdTgdjGEQ9tUglQOjdHhuiKNmZWXNA+iiZnbOAfRQPKNnZW6sbWFpbm5ldC12MS4womdoxCDAYcTY/B293tLXYEvkVo4/bQQZh6w3veS2ILWrOSSK36Jsds4B9FQko3JjdsQgYM9PRS/NJyNkzAbNG3dG2BE5jxFovvdWEMnP0EaeIS6jc25kxCBgz09FL80nI2TMBs0bd0bYETmPEWi+91YQyc/QRp4hLqR0eXBlo3BheQ==",
"txid": "RXWEB2KZVNQDSIJUU34KWBPEIUTVUPFT3DQ7NMMFXZJIOXN2NE3A",
}
`;

View File

@@ -0,0 +1,106 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ed25519 } from '@onekeyhq/core/src/secret/curves';
import { OneKeyInternalError } from '@onekeyhq/shared/src/errors';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import sdkAlgo from './sdkAlgo';
import type { ISdkAlgoEncodedTransaction } from './sdkAlgo';
import type { IEncodedTxAlgo } from './types';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
} from '../../types';
const curve: ICurveName = 'ed25519';
export default class CoreChainSoftware extends CoreChainApiBase {
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
curve,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
const encodedTx = unsignedTx.encodedTx as IEncodedTxAlgo;
const transaction = sdkAlgo.Transaction.from_obj_for_encoding(
sdkAlgo.decodeObj(
Buffer.from(encodedTx, 'base64'),
) as ISdkAlgoEncodedTransaction,
);
const [signature] = await signer.sign(transaction.bytesToSign());
const txid: string = transaction.txID();
const rawTx: string = Buffer.from(
sdkAlgo.encodeObj({
sig: signature,
txn: transaction.get_obj_for_encoding(),
}),
).toString('base64');
return {
txid,
rawTx,
};
}
override async signMessage(query: ICoreApiSignMsgPayload): Promise<string> {
throw new Error('Method not implemented.');
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
const { privateKeyRaw } = query;
const privateKey = bufferUtils.toBuffer(privateKeyRaw);
if (privateKey.length !== 32) {
throw new OneKeyInternalError('Invalid private key.');
}
const pub = ed25519.publicFromPrivate(privateKey);
return this.getAddressFromPublic({
publicKey: bufferUtils.bytesToHex(pub),
networkInfo: query.networkInfo,
});
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
const { publicKey } = query;
const address = sdkAlgo.encodeAddress(bufferUtils.toBuffer(publicKey));
return Promise.resolve({
address,
publicKey,
});
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
return this.baseGetAddressesFromHd(query, {
curve,
});
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,4 @@
import sdk from './sdkAlgo';
export * from './sdkAlgo';
export default sdk;

View File

@@ -0,0 +1,12 @@
// import * as sdk from 'algosdk/dist/esm/index.js';
// export default sdk as typeof import('algosdk');
import sdk from 'algosdk';
export type {
EncodedTransaction as ISdkAlgoEncodedTransaction,
Transaction as ISdkAlgoTransaction,
TransactionType as ISdkAlgoTransactionType,
} from 'algosdk';
export default sdk;

View File

@@ -0,0 +1 @@
export type IEncodedTxAlgo = string; // Base64 encoded string

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,142 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import { EMessageTypesAptos } from '../../types';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'aptos',
chainId: '1',
networkId: 'aptos--1',
networkImpl: 'aptos',
isTestnet: false,
},
hdAccountTemplate: "m/44'/637'/$$INDEX$$'/0'/0'",
hdAccounts: [
{
address:
'0xede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed6',
path: "m/44'/637'/0'/0'/0'",
publicKey:
'99b4709ae159e666615bdb5e0f0eb1f3738c7815c9a0c2173a824856859f1ea4',
privateKeyRaw:
'890c0f8ac6794dde7159241cab1d60cf76f492657fe7bc1b9115bcc00a675eb2',
},
],
txSamples: [
{
unsignedTx: {
inputs: [
{
address:
'0xede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed6',
publicKey:
'99b4709ae159e666615bdb5e0f0eb1f3738c7815c9a0c2173a824856859f1ea4',
value: {} as any,
},
],
outputs: [],
payload: {},
encodedTx: '',
rawTxUnsigned:
'ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed600000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed60800e1f505000000000a000000000000006400000000000000b15627650000000002',
},
signedTx: {
'txid': '',
'rawTx':
'ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed600000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed60800e1f505000000000a000000000000006400000000000000b15627650000000002002099b4709ae159e666615bdb5e0f0eb1f3738c7815c9a0c2173a824856859f1ea44020e5d9f7029a7a292208288bf0c33fb42ff3d3755f5a3da7c279aa75c9ee131b9b3d5bc36e4e0165baa0947b93a9b52e0ccce1c2dc203f16a5fb3688ab5e1e07',
},
},
],
msgSamples: [
{
unsignedMsg: {
type: EMessageTypesAptos.SIGN_MESSAGE,
message: JSON.stringify({
'message': 'This is a sample message',
'nonce': 12345,
'fullMessage':
'APTOS\napplication: dapp-example.onekeytest.com\nchainId: 1\nmessage: This is a sample message\nnonce: 12345',
'application': 'dapp-example.onekeytest.com',
'chainId': 1,
}),
},
signedMsg:
'0x9a2c04c32cfefa10c9416ce6274c015bb606473a3d67a7563354312d7a460f3046eb18b2919bae1deeb1afff178f8096396f6bc470c1c5d025cd7785449c2500',
},
],
});
// yarn jest packages/core/src/chains/apt/CoreChainSoftware.test.ts
describe('APT Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it('signMessage', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignMessageOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
msgSamples,
});
});
});

View File

@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`APT Core tests signTransaction 1`] = `
{
"rawTx": "ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed600000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed60800e1f505000000000a000000000000006400000000000000b15627650000000002002099b4709ae159e666615bdb5e0f0eb1f3738c7815c9a0c2173a824856859f1ea44020e5d9f7029a7a292208288bf0c33fb42ff3d3755f5a3da7c279aa75c9ee131b9b3d5bc36e4e0165baa0947b93a9b52e0ccce1c2dc203f16a5fb3688ab5e1e07",
"txid": "",
}
`;

View File

@@ -0,0 +1,152 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { BCS, TransactionBuilder, TxnBuilderTypes } from 'aptos';
// eslint-disable-next-line camelcase
import { sha3_256 } from 'js-sha3';
import { ed25519 } from '@onekeyhq/core/src/secret/curves';
import { OneKeyInternalError } from '@onekeyhq/shared/src/errors';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import {
addHexPrefix,
hexlify,
stripHexPrefix,
} from '@onekeyhq/shared/src/utils/hexUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
IUnsignedMessageAptos,
} from '../../types';
const curveName: ICurveName = 'ed25519';
async function deserializeTransactionAptos(
rawTx: string,
): Promise<TxnBuilderTypes.RawTransaction> {
const bytes = bufferUtils.toBuffer(rawTx);
const deserializer = new BCS.Deserializer(bytes);
const tx = TxnBuilderTypes.RawTransaction.deserialize(deserializer);
return Promise.resolve(tx);
}
async function buildSignedTx(
rawTxn: TxnBuilderTypes.RawTransaction,
senderPublicKey: string,
signature: string,
) {
const txSignature = new TxnBuilderTypes.Ed25519Signature(
bufferUtils.hexToBytes(signature),
);
const authenticator = new TxnBuilderTypes.TransactionAuthenticatorEd25519(
new TxnBuilderTypes.Ed25519PublicKey(
bufferUtils.hexToBytes(stripHexPrefix(senderPublicKey)),
),
txSignature,
);
const signRawTx = BCS.bcsToBytes(
new TxnBuilderTypes.SignedTransaction(rawTxn, authenticator),
);
return Promise.resolve({
txid: '',
rawTx: bufferUtils.bytesToHex(signRawTx),
});
}
export default class CoreChainSoftware extends CoreChainApiBase {
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
curve: curveName,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve: curveName,
});
const { rawTxUnsigned } = unsignedTx;
if (!rawTxUnsigned) {
throw new Error('rawTxUnsigned is undefined');
}
const senderPublicKey = unsignedTx.inputs?.[0]?.publicKey;
if (!senderPublicKey) {
throw new OneKeyInternalError('Unable to get sender public key.');
}
const rawTxn = await deserializeTransactionAptos(rawTxUnsigned);
const signingMessage = TransactionBuilder.getSigningMessage(rawTxn);
const [signature] = await signer.sign(bufferUtils.toBuffer(signingMessage));
const signatureHex = hexlify(signature, {
noPrefix: true,
});
return buildSignedTx(rawTxn, senderPublicKey, signatureHex);
}
override async signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
const unsignedMsg = payload.unsignedMsg as IUnsignedMessageAptos;
const signer = await this.baseGetSingleSigner({
payload,
curve: curveName,
});
const { fullMessage } = JSON.parse(unsignedMsg.message);
const [signature] = await signer.sign(Buffer.from(fullMessage));
return addHexPrefix(signature.toString('hex'));
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
const { publicKey } = query;
const pubkey = bufferUtils.toBuffer(publicKey);
// eslint-disable-next-line camelcase
const hash = sha3_256.create();
hash.update(pubkey);
hash.update('\x00');
const address = addHexPrefix(hash.hex());
return Promise.resolve({
address,
publicKey,
});
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
const { privateKeyRaw } = query;
const privateKey = bufferUtils.toBuffer(privateKeyRaw);
if (privateKey.length !== 32) {
throw new OneKeyInternalError('Invalid private key.');
}
const pub = ed25519.publicFromPrivate(privateKey);
return this.getAddressFromPublic({
publicKey: bufferUtils.bytesToHex(pub),
networkInfo: query.networkInfo,
});
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
return this.baseGetAddressesFromHd(query, {
curve: curveName,
});
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,112 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'bch',
chainId: '0',
networkId: 'bch--0',
networkImpl: 'bch',
isTestnet: false,
},
hdAccountTemplate: "m/44'/145'/$$INDEX$$'/0/0",
hdAccounts: [
{
address: 'bitcoincash:qpj2p3ykvwktp9uvx5dpkq3uv3zqd5n67qcjtlynnp',
addresses: {
'0/0': 'bitcoincash:qpj2p3ykvwktp9uvx5dpkq3uv3zqd5n67qcjtlynnp',
},
path: "m/44'/145'/0'",
relPaths: ['0/0'],
xpub: 'xpub6CsXcwbJS7Go9ZmTiQjZF6dG6mhmTBEoKzxymQUwMsynCXEKAMXrzh8oym3vehjorx16T7mGuqCRKkZ84Zfc7PKuKVkBcCLn46VZCUXPWTH',
xpvtRaw:
'0488ade403a5fb656980000000b5ac535a2528d4641f006a3170b5cde3810f47d2f27872e1faac0798dd9eb07f0067eb4917b9f4d446d31b2ba3db99fe4c55c39f6d4ca024f18016af865548cd86',
publicKey:
'023342708de6a3557949806aa20f4a2d37e4e3e8a3314ebc211e0ef99b98234763',
privateKeyRaw:
'72f3446b193e0ebd2755f57d1c5d10b08f53b8e598e9fb236af231a5b7f82bbe',
},
],
txSamples: [
{
encodedTx: '',
signedTx: {
'txid': '',
'rawTx': '',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/bch/CoreChainSoftware.test.ts
describe('BCH Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it.skip('signTransaction', async () => {
const coreApi = new CoreChainHd();
// TODO bch tx mock
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,91 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
isCashAddress,
isValidAddress,
toCashAddress,
toLegacyAddress,
} from 'bchaddrjs';
import { Psbt as PsbtBtcFork } from 'bitcoinforkjs';
import CoreChainSoftwareBtc from '../btc/CoreChainSoftware';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImportedBtc,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHdBtc,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ISignedTxPro,
} from '../../types';
import type { IBtcForkNetwork } from '../btc/types';
import type { Psbt } from 'bitcoinjs-lib';
export default class CoreChainSoftware extends CoreChainSoftwareBtc {
override decodeAddress(address: string): string {
if (
!isValidAddress(address) ||
(isCashAddress(address) && !address.startsWith('bitcoincash:'))
) {
throw new Error(`Invalid address: ${address}`);
}
if (isCashAddress(address)) {
return toLegacyAddress(address);
}
return address;
}
override encodeAddress(address: string): string {
if (!isValidAddress(address)) {
throw new Error(`Invalid address: ${address}`);
}
if (!isCashAddress(address)) {
return toCashAddress(address);
}
return address;
}
override getPsbt({ network }: { network: IBtcForkNetwork }): Psbt {
// @ts-expect-error
return new PsbtBtcFork({ network, forkCoin: 'bch' });
}
override signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
return super.signMessage(payload);
}
override signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
return super.signTransaction(payload);
}
override getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return super.getPrivateKeys(payload);
}
override getAddressFromPrivate(
query: ICoreApiGetAddressQueryImportedBtc,
): Promise<ICoreApiGetAddressItem> {
return super.getAddressFromPrivate(query);
}
override getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
return super.getAddressFromPublic(query);
}
override getAddressesFromHd(
query: ICoreApiGetAddressesQueryHdBtc,
): Promise<ICoreApiGetAddressesResult> {
return super.getAddressesFromHd(query);
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,114 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import { EAddressEncodings } from '../../types';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'btc',
chainId: '0',
networkId: 'btc--0',
networkImpl: 'btc',
isTestnet: false,
},
hdAccountTemplate: "m/49'/0'/$$INDEX$$'/0/0",
hdAccounts: [
{
address: '386yQdFWfbAuEUa1ctbZo9Lgacj3PhXs9R',
addresses: {
'0/0': '386yQdFWfbAuEUa1ctbZo9Lgacj3PhXs9R',
},
path: "m/49'/0'/0'",
relPaths: ['0/0'],
xpub: 'ypub6WoTEgqafv3Zx3Nk8zyMnYvK7ckrMy942G3mWzBpDSPJm8yXAUShZ31cH4jGQgUcbD8F1tY34nxrxxJi1ZAZAqFjacpdBmLGDVjpgxbEGKk',
xpvtRaw:
'049d7878032b357cdf80000000966c236dfa226d4ee87f9b9202a357a8f338a9fed2c1b355303ee83758cf142c0074cddc8d83dfcf62ab5ba18c4620c12f77c04bb3b75dd47ff3cffb8a8e25739f',
publicKey:
'03098891dd952dd6f6bde1489761d0befbfa31815e9c0e64058d12b83de852a18c',
privateKeyRaw:
'0f1ed8d7b952569b93d66e8727a532b8b7d95144b4c93ed3605491ac08f81f15',
},
],
txSamples: [
{
encodedTx: '',
signedTx: {
'txid': '',
'rawTx': '',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/btc/CoreChainSoftware.test.ts
describe('BTC Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
addressEncoding: EAddressEncodings.P2SH_P2WPKH,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it.skip('signTransaction', async () => {
const coreApi = new CoreChainHd();
// TODO BTC tx mock
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,149 @@
import { BIP32Factory } from 'bip32';
import {
address as BitcoinJsAddress,
Transaction,
crypto,
initEccLib,
} from 'bitcoinjs-lib';
import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371';
import { ECPairFactory } from 'ecpair';
import ecc from '../../../secret/nobleSecp256k1Wrapper';
import type { ICoreApiSignAccount, InputToSign } from '../../../types';
import type { IBtcForkNetwork, IBtcForkSigner } from '../types';
import type { BIP32API } from 'bip32/types/bip32';
import type { Psbt, networks } from 'bitcoinjs-lib';
import type { TinySecp256k1Interface } from 'bitcoinjs-lib/src/types';
import type { ECPairAPI } from 'ecpair/src/ecpair';
export * from './networks';
let bip32: BIP32API | undefined;
let ECPair: ECPairAPI | undefined;
let isEccInit = false;
export function initBitcoinEcc() {
if (!isEccInit) {
initEccLib(ecc as unknown as TinySecp256k1Interface);
isEccInit = true;
}
}
export function getBitcoinBip32() {
if (!bip32) {
initBitcoinEcc();
// @ts-expect-error
bip32 = BIP32Factory(ecc);
}
return bip32;
}
export function getBitcoinECPair() {
if (!ECPair) {
initBitcoinEcc();
// @ts-expect-error
ECPair = ECPairFactory(ecc);
}
return ECPair;
}
function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return crypto.taggedHash(
'TapTweak',
Buffer.concat(h ? [pubKey, h] : [pubKey]),
);
}
export function tweakSigner(
privKey: Buffer,
publicKey: Buffer,
opts: { tweakHash?: Buffer; network?: IBtcForkNetwork } = {},
): IBtcForkSigner {
let privateKey: Uint8Array | null = new Uint8Array(privKey.buffer);
if (!privateKey) {
throw new Error('Private key is required for tweaking signer!');
}
if (publicKey[0] === 3) {
privateKey = ecc.privateNegate(privateKey);
}
if (!privateKey) {
throw new Error('Private key is required for tweaking signer!');
}
const tweakedPrivateKey = ecc.privateAdd(
privateKey,
tapTweakHash(toXOnly(publicKey), opts.tweakHash),
);
if (!tweakedPrivateKey) {
throw new Error('Invalid tweaked private key!');
}
return getBitcoinECPair().fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
}
const TX_OP_RETURN_SIZE_LIMIT = 80;
export const loadOPReturn = (
opReturn: string,
opReturnSizeLimit: number = TX_OP_RETURN_SIZE_LIMIT,
) => {
const buffer = Buffer.from(opReturn);
return buffer.slice(0, opReturnSizeLimit);
};
export const isTaprootPath = (pathPrefix: string) =>
pathPrefix.startsWith(`m/86'/`);
export function getInputsToSignFromPsbt({
psbt,
psbtNetwork,
account,
}: {
account: ICoreApiSignAccount;
psbt: Psbt;
psbtNetwork: networks.Network;
}) {
const inputsToSign: InputToSign[] = [];
psbt.data.inputs.forEach((v, index) => {
let script: any = null;
let value = 0;
if (v.witnessUtxo) {
script = v.witnessUtxo.script;
value = v.witnessUtxo.value;
} else if (v.nonWitnessUtxo) {
const tx = Transaction.fromBuffer(v.nonWitnessUtxo);
const output = tx.outs[psbt.txInputs[index].index];
script = output.script;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
value = output.value;
}
const isSigned = v.finalScriptSig || v.finalScriptWitness;
if (script && !isSigned) {
const address = BitcoinJsAddress.fromOutputScript(script, psbtNetwork);
if (account.address === address) {
const pubKeyStr = account.pubKey as string;
if (!pubKeyStr) {
throw new Error('pubKey is empty');
}
inputsToSign.push({
index,
publicKey: pubKeyStr,
address,
sighashTypes: v.sighashType ? [v.sighashType] : undefined,
});
if (
(account.template as string).startsWith(`m/86'/`) &&
!v.tapInternalKey
) {
v.tapInternalKey = toXOnly(Buffer.from(pubKeyStr, 'hex'));
}
}
}
});
return inputsToSign;
}

View File

@@ -0,0 +1,192 @@
import { networks } from 'bitcoinjs-lib';
import { EAddressEncodings } from '../../../types';
import type { IBtcForkNetwork } from '../types';
const btc: IBtcForkNetwork = {
...networks.bitcoin,
segwitVersionBytes: {
[EAddressEncodings.P2SH_P2WPKH]: {
public: 0x049d7cb2,
private: 0x049d7878,
},
[EAddressEncodings.P2WPKH]: {
public: 0x04b24746,
private: 0x04b2430c,
},
[EAddressEncodings.P2TR]: {
public: 0x0488b21e,
private: 0x0488ade4,
},
},
};
const tbtc: IBtcForkNetwork = {
...networks.testnet,
segwitVersionBytes: {
[EAddressEncodings.P2SH_P2WPKH]: {
public: 0x044a5262,
private: 0x044a4e28,
},
[EAddressEncodings.P2WPKH]: {
public: 0x045f1cf6,
private: 0x045f18bc,
},
[EAddressEncodings.P2TR]: {
public: 0x043587cf,
private: 0x04358394,
},
},
};
const rbtc: IBtcForkNetwork = {
...networks.regtest,
segwitVersionBytes: {
[EAddressEncodings.P2SH_P2WPKH]: {
public: 0x044a5262,
private: 0x044a4e28,
},
[EAddressEncodings.P2WPKH]: {
public: 0x045f1cf6,
private: 0x045f18bc,
},
},
};
const ltc: IBtcForkNetwork = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bech32: 'ltc',
bip32: {
public: 0x019da462,
private: 0x019d9cfe,
},
segwitVersionBytes: {
[EAddressEncodings.P2SH_P2WPKH]: {
public: 0x01b26ef6,
private: 0x01b26792,
},
[EAddressEncodings.P2WPKH]: {
public: 0x04b24746,
private: 0x04b2430c,
},
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
};
const bch: IBtcForkNetwork = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: '',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
forkId: 0x00,
};
const doge: IBtcForkNetwork = {
messagePrefix: '\x19Dogecoin Signed Message:\n',
bech32: '',
bip32: {
public: 0x02facafd,
private: 0x02fac398,
},
pubKeyHash: 0x1e,
scriptHash: 0x16,
wif: 0x9e,
};
const btg: IBtcForkNetwork = {
messagePrefix: '\x1dBitcoin Gold Signed Message:\n',
bech32: 'btg',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x26,
scriptHash: 0x17,
wif: 0x80,
};
const dgb: IBtcForkNetwork = {
messagePrefix: '\x19DigiByte Signed Message:\n',
bech32: 'dgb',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1e,
scriptHash: 0x3f,
wif: 0xb0,
};
const nmc: IBtcForkNetwork = {
messagePrefix: '\x19Namecoin Signed Message:\n',
bech32: '',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x34,
scriptHash: 0x05,
wif: 0x80,
};
const vtc: IBtcForkNetwork = {
messagePrefix: '\x19Vertcoin Signed Message:\n',
bech32: 'vtc',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x05,
wif: 0x80,
};
const dash: IBtcForkNetwork = {
messagePrefix: '\x19DarkCoin Signed Message:\n',
bech32: '',
bip32: {
public: 0x02fe52cc,
private: 0x02fe52f8,
},
pubKeyHash: 0x4c,
scriptHash: 0x10,
wif: 0xcc,
};
const extendedNetworks: Record<string, IBtcForkNetwork> = {
btc,
tbtc,
rbtc,
ltc,
bch,
doge,
btg,
dgb,
nmc,
vtc,
dash,
};
export type IBtcForkExtendedNetworks = keyof typeof extendedNetworks;
export function getBtcForkNetwork(
chainCode: string | undefined,
): IBtcForkNetwork {
if (!chainCode) {
throw new Error('getBtcForkNetwork ERROR: chainCode is undefined');
}
const network = extendedNetworks[chainCode];
network.networkChainCode = chainCode;
if (typeof network === 'undefined') {
throw new Error(`Network not found. chainCode: ${chainCode}`);
}
return network;
}

View File

@@ -0,0 +1,31 @@
import type { EAddressEncodings } from '../../types';
import type BigNumber from 'bignumber.js';
import type {
NonWitnessUtxo,
RedeemScript,
TapInternalKey,
WitnessUtxo,
} from 'bip174/src/lib/interfaces';
import type { Network, Signer } from 'bitcoinjs-lib';
export interface IBtcForkNetwork extends Network {
networkChainCode?: string;
// Extends the network interface to support:
// - segwit address version bytes
segwitVersionBytes?: Partial<Record<EAddressEncodings, Network['bip32']>>;
forkId?: number; // bch
}
export type IBtcForkSigner = Signer;
export type IBtcForkTransactionMixin = {
nonWitnessUtxo?: NonWitnessUtxo;
witnessUtxo?: WitnessUtxo;
redeemScript?: RedeemScript;
tapInternalKey?: TapInternalKey;
};
export type IBtcForkUTXO = {
txid: string;
vout: number;
value: BigNumber;
};

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,122 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'cfx',
chainId: '1029',
networkId: 'cfx--1029',
networkImpl: 'cfx',
isTestnet: false,
},
hdAccountTemplate: "m/44'/503'/0'/0/$$INDEX$$",
hdAccounts: [
{
address: '0x1a8348d02ae925bb86e4a4ac031ec42c28a4b5dc',
addresses: {
'cfx--1029': 'cfx:aarjgwgufnywns6g6wwm2a282u0cvkfz5uekxw2hsm',
},
path: "m/44'/503'/0'/0/0",
publicKey:
'02474b4c1b1f3830fc0c161badc1b90ef06513593c67adaaab53e1140ca94cb652',
privateKeyRaw:
'468e1d6a29086e162746c66f491ddc3cbe3f11a8d826f8e1d4368e8f9bb1275c',
},
],
txSamples: [
{
encodedTx: {
'from': 'cfx:aarjgwgufnywns6g6wwm2a282u0cvkfz5uekxw2hsm',
'to': 'cfx:aarjgwgufnywns6g6wwm2a282u0cvkfz5uekxw2hsm',
'value': '0',
'data': '0x',
'gas': '0x5208',
'gasLimit': '0x5208',
'gasPrice': '1000000000',
'nonce': 0,
'epochHeight': 81203903,
'chainId': 1029,
'storageLimit': '0',
},
signedTx: {
'digest':
'0x830f7330fe140d0515381cad22f7796b2e2f7a5c451641797f7bb4ee5aed7eeb',
'txid':
'0xf94d5db32cdfd3c644274c17003a975a2082eb4fffcea4900b5e8a9c10a11884',
'rawTx':
'0xf86de980843b9aca00825208941a8348d02ae925bb86e4a4ac031ec42c28a4b5dc80808404d712bf8204058001a0c57de20f6d4c8c3785e675cfc76a5fad0fcb6e80bb15edb020ee8c71dfb54b27a003158bbebc413d8ecbd69475f60d8b6b1156c18aeb46d4afd7028a96cdcedfa3',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/cfx/CoreChainSoftware.test.ts
describe('CFX Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CFX Core tests signTransaction 1`] = `
{
"digest": "0x830f7330fe140d0515381cad22f7796b2e2f7a5c451641797f7bb4ee5aed7eeb",
"rawTx": "0xf86de980843b9aca00825208941a8348d02ae925bb86e4a4ac031ec42c28a4b5dc80808404d712bf8204058001a0c57de20f6d4c8c3785e675cfc76a5fad0fcb6e80bb15edb020ee8c71dfb54b27a003158bbebc413d8ecbd69475f60d8b6b1156c18aeb46d4afd7028a96cdcedfa3",
"txid": "0xf94d5db32cdfd3c644274c17003a975a2082eb4fffcea4900b5e8a9c10a11884",
}
`;

View File

@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { uncompressPublicKey } from '@onekeyhq/core/src/secret';
import { secp256k1 } from '@onekeyhq/core/src/secret/curves';
import { checkIsDefined } from '@onekeyhq/shared/src/utils/assertUtils';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import {
cfxAddressToEthAddress,
pubkeyToCfxAddress,
signTransactionWithSigner,
} from './sdkCfx';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
} from '../../types';
const curve: ICurveName = 'secp256k1';
export default class CoreChainSoftware extends CoreChainApiBase {
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
curve,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
// throw new Error('Method not implemented.');
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
return signTransactionWithSigner(unsignedTx, signer);
}
override async signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
throw new Error('Method not implemented.');
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
const { privateKeyRaw } = query;
const privateKey = bufferUtils.toBuffer(privateKeyRaw);
const pub = secp256k1.publicFromPrivate(privateKey);
return this.getAddressFromPublic({
publicKey: bufferUtils.bytesToHex(pub),
networkInfo: query.networkInfo,
});
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
const { publicKey } = query;
const compressedPublicKey = bufferUtils.toBuffer(publicKey);
const uncompressedPublicKey = uncompressPublicKey(
curve,
compressedPublicKey,
);
const { chainId, networkId } = checkIsDefined(query.networkInfo);
const cfxAddress = await pubkeyToCfxAddress(
uncompressedPublicKey,
checkIsDefined(chainId),
);
const ethAddress = await cfxAddressToEthAddress(cfxAddress);
return Promise.resolve({
address: ethAddress,
addresses: {
[checkIsDefined(networkId)]: cfxAddress,
},
publicKey,
});
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
return this.baseGetAddressesFromHd(query, {
curve,
});
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,4 @@
// @ts-ignore
import sdk from 'js-conflux-sdk/src/index.js';
export const conflux = sdk as typeof import('js-conflux-sdk');

View File

@@ -0,0 +1,67 @@
import {
encode as toCfxAddress,
decode as toEthAddress,
} from '@conflux-dev/conflux-address-js';
import { hexZeroPad } from '@ethersproject/bytes';
import { keccak256 } from '@ethersproject/keccak256';
import { conflux } from './conflux';
import type { ISigner } from '../../../base/ChainSigner';
import type { ISignedTxPro, IUnsignedTxPro } from '../../../types';
import type { IEncodedTxCfx } from '../types';
export * from './conflux';
export { hexZeroPad, keccak256 };
export function ethAddressToCfxAddress(address: string): string {
return `0x1${address.toLowerCase().slice(1)}`;
}
export function pubkeyToCfxAddress(
uncompressPubKey: Buffer,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
chainId: string,
): Promise<string> {
const pubkey = uncompressPubKey.slice(1);
const ethAddress = ethAddressToCfxAddress(keccak256(pubkey).slice(-40));
const networkID = parseInt(chainId);
return Promise.resolve(toCfxAddress(ethAddress, networkID));
}
export async function cfxAddressToEthAddress(address: string) {
return Promise.resolve(
`0x${toEthAddress(address).hexAddress.toString('hex')}`,
);
}
const { Transaction } = conflux;
export async function signTransactionWithSigner(
unsignedTx: IUnsignedTxPro,
signer: ISigner,
): Promise<ISignedTxPro> {
const unsignedTransaction = new Transaction(
unsignedTx.encodedTx as IEncodedTxCfx,
);
const digest = keccak256(unsignedTransaction.encode(false));
const [sig, recoveryParam] = await signer.sign(
Buffer.from(digest.slice(2), 'hex'),
);
const [r, s]: [Buffer, Buffer] = [sig.slice(0, 32), sig.slice(32)];
const signedTransaction = new Transaction({
...(unsignedTx.encodedTx as IEncodedTxCfx),
r: hexZeroPad(`0x${r.toString('hex')}`, 32),
s: hexZeroPad(`0x${s.toString('hex')}`, 32),
v: recoveryParam,
});
return {
digest,
txid: signedTransaction.hash,
rawTx: signedTransaction.serialize(),
};
}

View File

@@ -0,0 +1,16 @@
export type IEncodedTxCfx = {
from: string;
to: string;
value: string;
data: string;
hash?: string;
nonce?: number;
gas?: string;
gasFee?: string;
gasPrice?: string;
gasLimit?: string;
storageLimit?: string;
chainId?: number;
epochHeight?: number;
contract?: string;
};

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,163 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'cosmos',
chainId: '4',
networkId: 'cosmos--cosmoshub-4',
networkImpl: 'cosmos',
isTestnet: false,
addressPrefix: 'cosmos',
},
hdAccountTemplate: "m/44'/118'/0'/0/$$INDEX$$",
hdAccounts: [
{
address: 'b7ded9bc6b75abab9458df44420c7c5f5457e077',
addresses: {
'cosmos--cosmoshub-4': 'cosmos1kl0dn0rtwk46h9zcmazyyrruta290crh7wt6qp',
},
path: "m/44'/118'/0'/0/0",
publicKey:
'03352ac3058d7f088ae0791044874279340a63d1b7c10fb395db5a48cb9488b744',
privateKeyRaw:
'a7bb0633b951bbbb2ae43f15ac3e57fa0c83e2b6a45fccb5aa6dc5642d9d031a',
},
],
txSamples: [
{
unsignedTx: {
inputs: [
{
address: 'b7ded9bc6b75abab9458df44420c7c5f5457e077',
publicKey:
'03352ac3058d7f088ae0791044874279340a63d1b7c10fb395db5a48cb9488b744',
value: {} as any,
},
],
outputs: [],
payload: {},
encodedTx: {
'signDoc': {
'chain_id': 'cosmoshub-4',
'account_number': '1878610',
'sequence': '0',
'fee': {
'amount': [{ 'denom': 'uatom', 'amount': '2394' }],
'gas': '95752',
},
'msgs': [
{
'type': 'cosmos-sdk/MsgSend',
'value': {
'from_address':
'cosmos1kl0dn0rtwk46h9zcmazyyrruta290crh7wt6qp',
'to_address': 'cosmos1kl0dn0rtwk46h9zcmazyyrruta290crh7wt6qp',
'amount': [{ 'amount': '10000', 'denom': 'uatom' }],
},
},
],
'memo': '',
},
'mode': 'amino',
'msg': {
'aminoMsgs': [
{
'type': 'cosmos-sdk/MsgSend',
'value': {
'from_address':
'cosmos1kl0dn0rtwk46h9zcmazyyrruta290crh7wt6qp',
'to_address': 'cosmos1kl0dn0rtwk46h9zcmazyyrruta290crh7wt6qp',
'amount': [{ 'amount': '10000', 'denom': 'uatom' }],
},
},
],
'protoMsgs': [
{
'typeUrl': '/cosmos.bank.v1beta1.MsgSend',
'value':
'0a2d636f736d6f73316b6c30646e307274776b343668397a636d617a79797272757461323930637268377774367170122d636f736d6f73316b6c30646e307274776b343668397a636d617a797972727574613239306372683777743671701a0e0a057561746f6d12053130303030',
},
],
},
} as any,
},
signedTx: {
'txid': '',
'rawTx':
'CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLWNvc21vczFrbDBkbjBydHdrNDZoOXpjbWF6eXlycnV0YTI5MGNyaDd3dDZxcBItY29zbW9zMWtsMGRuMHJ0d2s0Nmg5emNtYXp5eXJydXRhMjkwY3JoN3d0NnFwGg4KBXVhdG9tEgUxMDAwMBJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDNSrDBY1/CIrgeRBEh0J5NApj0bfBD7OV21pIy5SIt0QSBAoCCH8SEwoNCgV1YXRvbRIEMjM5NBCI7AUaQOYpQZZf8Mx6ABGNmQUnTWx9C9Vj4ZMHhAnr2e84kHh6Q4/GnEE4cEi2A4E1/3ow5NgvkZDD1iCA+O/2V6L+FzQ=',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/cosmos/CoreChainSoftware.test.ts
describe('COSMOS Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`COSMOS Core tests signTransaction 1`] = `
{
"rawTx": "CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLWNvc21vczFrbDBkbjBydHdrNDZoOXpjbWF6eXlycnV0YTI5MGNyaDd3dDZxcBItY29zbW9zMWtsMGRuMHJ0d2s0Nmg5emNtYXp5eXJydXRhMjkwY3JoN3d0NnFwGg4KBXVhdG9tEgUxMDAwMBJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDNSrDBY1/CIrgeRBEh0J5NApj0bfBD7OV21pIy5SIt0QSBAoCCH8SEwoNCgV1YXRvbRIEMjM5NBCI7AUaQOYpQZZf8Mx6ABGNmQUnTWx9C9Vj4ZMHhAnr2e84kHh6Q4/GnEE4cEi2A4E1/3ow5NgvkZDD1iCA+O/2V6L+FzQ=",
"txid": "",
}
`;

View File

@@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { sha256 } from '@noble/hashes/sha256';
import { OneKeyInternalError } from '@onekeyhq/shared/src/errors';
import { checkIsDefined } from '@onekeyhq/shared/src/utils/assertUtils';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import {
baseAddressToAddress,
pubkeyToBaseAddress,
serializeSignedTx,
serializeTxForSignature,
} from './sdkCosmos';
import type { IEncodedTxCosmos } from './types';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
} from '../../types';
const curve: ICurveName = 'secp256k1';
export default class CoreChainSoftware extends CoreChainApiBase {
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
curve,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
// throw new Error('Method not implemented.');
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
const encodedTx = unsignedTx.encodedTx as IEncodedTxCosmos;
const txBytes = bufferUtils.toBuffer(
sha256(serializeTxForSignature(encodedTx)),
);
const [signature] = await signer.sign(txBytes);
const senderPublicKey = unsignedTx.inputs?.[0]?.publicKey;
if (!senderPublicKey) {
throw new OneKeyInternalError('Unable to get sender public key.');
}
const rawTxBytes = serializeSignedTx({
txWrapper: encodedTx,
signature: {
signatures: [signature],
},
publicKey: {
pubKey: senderPublicKey,
},
});
const txid = '';
return {
txid,
rawTx: Buffer.from(rawTxBytes).toString('base64'),
};
}
override async signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
throw new Error('Method not implemented.');
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
const { privateKeyRaw } = query;
const privateKey = bufferUtils.toBuffer(privateKeyRaw);
const pub = this.baseGetCurve(curve).publicFromPrivate(privateKey);
return this.getAddressFromPublic({
publicKey: bufferUtils.bytesToHex(pub),
networkInfo: query.networkInfo,
});
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
const { publicKey, networkInfo } = query;
const address = pubkeyToBaseAddress(
curve,
bufferUtils.hexToBytes(publicKey),
);
const addressCosmos = baseAddressToAddress(
checkIsDefined(networkInfo?.addressPrefix),
address,
);
return Promise.resolve({
address,
addresses: {
[networkInfo.networkId]: addressCosmos,
},
publicKey,
});
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
return this.baseGetAddressesFromHd(query, {
curve,
});
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,46 @@
import type { StdMsg } from './amino/types';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
export type AnyHex = {
typeUrl: string;
value: string;
};
export type ProtoMsgsOrWithAminoMsgs = {
// AminoMsg sent locally must contain ProtoMsg
aminoMsgs: StdMsg[];
protoMsgs: AnyHex[];
// // Add rlp types data if you need to support ethermint with ledger.
// // Must include `MsgValue`.
// rlpTypes?: Record<
// string,
// Array<{
// name: string;
// type: string;
// }>
// >;
};
export interface ITxMsgBuilder {
makeSendNativeMsg(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
): AnyHex | StdMsg;
makeSendCwTokenMsg(
sender: string,
contract: string,
toAddress: string,
value: string,
): AnyHex | StdMsg;
makeExecuteContractMsg(
sender: string,
contract: string,
msg: object,
funds?: Array<Coin>,
): AnyHex | StdMsg;
}

View File

@@ -0,0 +1,84 @@
import { ripemd160 } from '@noble/hashes/ripemd160';
import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import bech32 from 'bech32';
import type { ICurveName } from '../../../types';
const secp256k1PubkeyToRawAddress = (pubkey: Uint8Array): Uint8Array => {
if (pubkey.length !== 33) {
throw new Error(
`Invalid Secp256k1 pubkey length (compressed): ${pubkey.length}`,
);
}
return ripemd160(sha256(pubkey));
};
const ed25519PubkeyToRawAddress = (pubkey: Uint8Array): Uint8Array => {
if (pubkey.length !== 32) {
throw new Error(`Invalid Ed25519 pubkey length: ${pubkey.length}`);
}
return sha256(pubkey).slice(0, 20);
};
export const pubkeyToBaseAddress = (
curve: ICurveName,
pubkey: Uint8Array,
): string => {
const digest =
curve === 'secp256k1'
? secp256k1PubkeyToRawAddress(pubkey)
: ed25519PubkeyToRawAddress(pubkey);
return bytesToHex(digest);
};
export const pubkeyToAddress = (
curve: ICurveName,
prefix: string,
pubkey: Uint8Array,
): string => {
const digest = pubkeyToBaseAddress(curve, pubkey);
return bech32.encode(prefix, bech32.toWords(hexToBytes(digest)));
};
export const baseAddressToAddress = (
prefix: string,
baseAddress: string,
): string => bech32.encode(prefix, bech32.toWords(hexToBytes(baseAddress)));
export const isValidAddress = (
input: string,
requiredPrefix: string,
): boolean => {
try {
const { prefix, words } = bech32.decode(input);
if (prefix !== requiredPrefix) {
return false;
}
const data = bech32.fromWords(words);
return data.length === 20;
} catch {
return false;
}
};
export const isValidContractAddress = (
input: string,
requiredPrefix: string,
): boolean => {
try {
const { prefix, words } = bech32.decode(input);
if (prefix !== requiredPrefix) {
return false;
}
const data = bech32.fromWords(words);
// example: juno 32 length
// example: terra 20 length
return data.length === 32 || data.length === 20;
} catch {
return false;
}
};

View File

@@ -0,0 +1,40 @@
import { TransactionWrapper } from '../wrapper';
import type { ProtoMsgsOrWithAminoMsgs } from '../ITxMsgBuilder';
import type { TxBuilder } from '../txBuilder';
export class TxAminoBuilder implements TxBuilder {
makeTxWrapper(
messages: ProtoMsgsOrWithAminoMsgs,
params: {
memo: string;
gasLimit: string;
feeAmount: string;
pubkey: Uint8Array;
mainCoinDenom: string;
chainId: string;
accountNumber: string;
nonce: string;
},
): TransactionWrapper {
return TransactionWrapper.fromAminoSignDoc(
{
chain_id: params.chainId,
account_number: params.accountNumber,
sequence: params.nonce,
fee: {
amount: [
{
amount: params.feeAmount,
denom: params.mainCoinDenom,
},
],
gas: params.gasLimit,
},
msgs: messages.aminoMsgs,
memo: params.memo,
},
messages,
);
}
}

View File

@@ -0,0 +1,181 @@
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
import { SignDoc, TxBody, TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx';
import { MessageType } from '../message';
import { defaultAminoMsgOpts } from './types';
import type { StdMsg } from './types';
import type { ITxMsgBuilder } from '../ITxMsgBuilder';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
import type { Any } from 'cosmjs-types/google/protobuf/any';
export function decodeTxBody(txBody: Uint8Array): TxBody {
return TxBody.decode(txBody);
}
export function makeSignBytes({
accountNumber,
authInfoBytes,
bodyBytes,
chainId,
}: SignDoc): Uint8Array {
const signDoc = SignDoc.fromPartial({
authInfoBytes,
bodyBytes,
chainId,
accountNumber,
});
return SignDoc.encode(signDoc).finish();
}
export function makeMsgSend(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
): Any {
return {
typeUrl: MessageType.SEND,
value: MsgSend.encode(
MsgSend.fromPartial({
fromAddress,
toAddress,
amount: [
{
amount: value,
denom,
},
],
}),
).finish(),
};
}
function removeNull(obj: any): any {
if (obj !== null && typeof obj === 'object') {
return Object.entries(obj)
.filter(([, v]) => v != null)
.reduce(
(acc, [k, v]) => ({
...acc,
[k]: v === Object(v) && !Array.isArray(v) ? removeNull(v) : v,
}),
{},
);
}
return obj;
}
export function makeMsgExecuteContract(
sender: string,
contract: string,
msg: object,
funds?: Array<Coin>,
): Any {
return {
typeUrl: MessageType.EXECUTE_CONTRACT,
value: MsgExecuteContract.encode(
MsgExecuteContract.fromPartial({
sender,
contract,
msg: Buffer.from(JSON.stringify(removeNull(msg))),
funds,
}),
).finish(),
};
}
export function makeTerraMsgExecuteContract(
sender: string,
contract: string,
msg: object,
funds?: Array<Coin>,
): Any {
return {
typeUrl: MessageType.TERRA_EXECUTE_CONTRACT,
value: MsgExecuteContract.encode(
MsgExecuteContract.fromPartial({
sender,
contract,
msg: Buffer.from(JSON.stringify(removeNull(msg))),
funds,
}),
).finish(),
};
}
export function makeTxRawBytes(
bodyBytes: Uint8Array,
authInfoBytes: Uint8Array,
signatures: Uint8Array[],
): Uint8Array {
return TxRaw.encode(
TxRaw.fromPartial({
bodyBytes,
authInfoBytes,
signatures,
}),
).finish();
}
export class TxAminoMsgBuilder implements ITxMsgBuilder {
makeExecuteContractMsg(
sender: string,
contract: string,
msg: object,
funds?: Coin[] | undefined,
) {
return {
type: defaultAminoMsgOpts.executeWasm.type,
value: {
sender,
contract,
msg,
funds,
},
};
}
makeSendNativeMsg(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
): StdMsg {
return {
type: defaultAminoMsgOpts.send.native.type,
value: {
from_address: fromAddress,
to_address: toAddress,
amount: [
{
amount: value,
denom,
},
],
},
};
}
makeSendCwTokenMsg(
sender: string,
contract: string,
toAddress: string,
value: string,
) {
return this.makeExecuteContractMsg(
sender,
contract,
{
transfer: {
recipient: toAddress,
amount: value,
},
},
[],
);
}
}

View File

@@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { UnknownMessage } from '../message';
import { defaultAminoMsgOpts } from './types';
import type { StdMsg } from './types';
import type { UnpackedMessage } from '../proto/protoDecode';
interface AminoFactory {
decode: (message: any) => any;
}
export class AminoDecode {
protected typeFactoryMap: Map<string, AminoFactory> = new Map();
unpackMessage(aminoAny: StdMsg): UnpackedMessage {
const factory = this.typeFactoryMap.get(aminoAny.type);
if (!factory) {
return new UnknownMessage(aminoAny.type, aminoAny.value);
}
const unpacked = factory.decode(aminoAny.value);
return {
typeUrl: aminoAny.type,
value: aminoAny.value,
unpacked,
};
}
registerFactory(type: string, message: AminoFactory): void {
this.typeFactoryMap.set(type, message);
}
}
export const defaultAminoDecodeRegistry = new AminoDecode();
defaultAminoDecodeRegistry.registerFactory(
defaultAminoMsgOpts.send.native.type,
{
decode: (message: any) => ({
fromAddress: message.from_address,
toAddress: message.to_address,
amount: message.amount,
}),
},
);
defaultAminoDecodeRegistry.registerFactory(
defaultAminoMsgOpts.executeWasm.type,
{
decode: (message: any) => ({
sender: message.sender,
contract: message.contract,
msg: message.msg,
funds: message.funds,
}),
},
);

View File

@@ -0,0 +1,98 @@
import type { ICosmosCoin } from '../../types';
export interface CosmosMsgOpts {
readonly send: {
readonly native: MsgOpt;
};
readonly ibcTransfer: MsgOpt;
readonly delegate: MsgOpt;
readonly undelegate: MsgOpt;
readonly redelegate: MsgOpt;
// The gas multiplication per rewards.
readonly withdrawRewards: MsgOpt;
readonly govVote: MsgOpt;
readonly executeWasm: MsgOpt;
}
export const defaultAminoMsgOpts: CosmosMsgOpts = {
send: {
native: {
type: 'cosmos-sdk/MsgSend',
gas: 80000,
},
},
ibcTransfer: {
type: 'cosmos-sdk/MsgTransfer',
gas: 450000,
},
delegate: {
type: 'cosmos-sdk/MsgDelegate',
gas: 250000,
},
undelegate: {
type: 'cosmos-sdk/MsgUndelegate',
gas: 250000,
},
redelegate: {
type: 'cosmos-sdk/MsgBeginRedelegate',
gas: 250000,
},
// The gas multiplication per rewards.
withdrawRewards: {
type: 'cosmos-sdk/MsgWithdrawDelegationReward',
gas: 140000,
},
govVote: {
type: 'cosmos-sdk/MsgVote',
gas: 250000,
},
executeWasm: {
type: 'wasm/MsgExecuteContract',
gas: 250000,
},
};
export interface MsgOpt {
readonly type: string;
readonly gas: number;
}
export interface StdMsg {
type: string;
value: any;
}
export interface StdPublickey {
readonly type: string;
readonly value: any;
}
export interface StdFee {
amount: ICosmosCoin[];
gas: string;
}
export interface StdSignature {
readonly pub_key: StdPublickey;
readonly signature: string;
}
export interface StdSignDoc {
readonly chain_id: string;
readonly account_number: string;
readonly sequence: string;
fee: StdFee;
readonly msgs: StdMsg[];
readonly memo: string;
}
export interface AminoSignTx extends StdSignDoc {
readonly signatures: StdSignature[];
}
export interface StdTx {
readonly msg: readonly StdMsg[];
readonly fee: StdFee;
readonly signatures: readonly StdSignature[];
readonly memo: string | undefined;
}

View File

@@ -0,0 +1,13 @@
export * from './address';
export * from './txBuilder';
export * from './message';
export * from './publickey';
export * from './wrapper';
export * from './wrapper/utils';
export * from './txMsgBuilder';
export * from './amino/types';
export * from './amino/TxAminoBuilder';
export * from './proto/protoDecode';
export * from './query/IQuery';
export * from './query/MintScanQuery';
export * as mintScanTypes from './query/mintScanTypes';

View File

@@ -0,0 +1,62 @@
import type { ICosmosCoin } from '../types';
import type { Any } from 'cosmjs-types/google/protobuf/any';
export enum MessageType {
SEND = '/cosmos.bank.v1beta1.MsgSend',
DELEGATE = '/cosmos.staking.v1beta1.MsgDelegate',
UNDELEGATE = '/cosmos.staking.v1beta1.MsgUndelegate',
REDELEGATE = '/cosmos.staking.v1beta1.MsgBeginRedelegate',
CREATE_VALIDATOR = '/cosmos.staking.v1beta1.MsgCreateValidator',
EDIT_VALIDATOR = '/cosmos.staking.v1beta1.MsgEditValidator',
WITHDRAW_DELEGATOR_REWARD = '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward',
WITHDRAW_VALIDATOR_COMMISSION = '/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission',
SET_WITHDRAW_ADDRESS = '/cosmos.distribution.v1beta1.MsgSetWithdrawAddress',
VOTE = '/cosmos.gov.v1beta1.MsgVote',
SUBMIT_PROPOSAL = '/cosmos.gov.v1beta1.MsgSubmitProposal',
DEPOSIT = '/cosmos.gov.v1beta1.MsgDeposit',
UNJAIL = '/cosmos.slashing.v1beta1.MsgUnjail',
IBC_TRANSFER = '/ibc.applications.transfer.v1.MsgTransfer',
GRANT = '/cosmos.authz.v1beta1.MsgGrant',
REVOKE = '/cosmos.authz.v1beta1.MsgRevoke',
INSTANTIATE_CONTRACT = '/cosmwasm.wasm.v1.MsgInstantiateContract',
EXECUTE_CONTRACT = '/cosmwasm.wasm.v1.MsgExecuteContract',
TERRA_EXECUTE_CONTRACT = '/terra.wasm.v1beta1.MsgExecuteContract',
}
export class UnknownMessage implements Any {
protected readonly _typeUrl: string;
protected readonly _value: Uint8Array;
constructor(_typeUrl: string, _value: Uint8Array) {
this._typeUrl = _typeUrl;
this._value = _value;
}
get typeUrl(): string {
return this._typeUrl;
}
get value(): Uint8Array {
return this._value;
}
toJSON() {
return {
type_url: this._typeUrl,
value: Buffer.from(this._value).toString('base64'),
};
}
}
// ======== RPC ========
export interface Message {
'@type': string;
}
export interface SendMessage extends Message {
readonly fromAddress: string;
readonly toAddress: string;
amount: ICosmosCoin[];
}

View File

@@ -0,0 +1,132 @@
import { hexToBytes } from '@noble/hashes/utils';
import { PubKey } from 'cosmjs-types/cosmos/crypto/ed25519/keys';
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing';
import { AuthInfo, TxBody } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { Any } from 'cosmjs-types/google/protobuf/any';
import Long from 'long';
import { TransactionWrapper } from '../wrapper';
import type { ProtoMsgsOrWithAminoMsgs } from '../ITxMsgBuilder';
import type { TxBuilder } from '../txBuilder';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
import type { SignDoc, SignerInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
export class TxProtoBuilder implements TxBuilder {
private makeTxBodyBytes(body: Partial<TxBody>): Uint8Array {
return TxBody.encode(
TxBody.fromPartial({
...body,
}),
).finish();
}
private encodePubkey(pubkey: Uint8Array): Any {
const pubkeyProto = PubKey.fromPartial({
key: pubkey,
});
return Any.fromPartial({
typeUrl: '/cosmos.crypto.secp256k1.PubKey',
value: Uint8Array.from(PubKey.encode(pubkeyProto).finish()),
});
}
private makeSignDoc(
bodyBytes: Uint8Array,
authInfoBytes: Uint8Array,
chainId: string,
accountNumber: Long,
): SignDoc {
return {
bodyBytes,
authInfoBytes,
chainId,
accountNumber,
};
}
/**
* Create signer infos from the provided signers.
*
* This implementation does not support different signing modes for the different signers.
*/
private makeSignerInfos(
signers: ReadonlyArray<{ readonly pubkey: any; readonly sequence: Long }>,
signMode: SignMode,
): SignerInfo[] {
return signers.map(
({ pubkey, sequence }): SignerInfo => ({
publicKey: pubkey,
modeInfo: {
single: { mode: signMode },
},
sequence,
}),
);
}
/**
* Creates and serializes an AuthInfo document.
*
* This implementation does not support different signing modes for the different signers.
*/
private makeAuthInfoBytes(
signers: ReadonlyArray<{ readonly pubkey: any; readonly sequence: Long }>,
feeAmount: readonly Coin[],
gasLimit: Long,
signMode = SignMode.SIGN_MODE_DIRECT,
): Uint8Array {
const authInfo = {
signerInfos: this.makeSignerInfos(signers, signMode),
fee: {
amount: [...feeAmount],
gasLimit,
},
};
return AuthInfo.encode(AuthInfo.fromPartial(authInfo)).finish();
}
makeTxWrapper(
messages: ProtoMsgsOrWithAminoMsgs,
params: {
memo: string;
gasLimit: string;
feeAmount: string;
pubkey: Uint8Array;
mainCoinDenom: string;
chainId: string;
accountNumber: string;
nonce: string;
},
): TransactionWrapper {
const bodyBytes = this.makeTxBodyBytes({
messages: messages.protoMsgs.map((msg) => ({
typeUrl: msg.typeUrl,
value: hexToBytes(msg.value),
})),
memo: params.memo,
});
const encodePub = this.encodePubkey(params.pubkey);
const authBytes = this.makeAuthInfoBytes(
[{ pubkey: encodePub, sequence: Long.fromString(params.nonce) }],
[
{
amount: params.feeAmount,
denom: params.mainCoinDenom,
},
],
Long.fromString(params.gasLimit),
);
return TransactionWrapper.fromDirectSignDoc(
this.makeSignDoc(
bodyBytes,
authBytes,
params.chainId,
Long.fromString(params.accountNumber),
),
messages,
);
}
}

View File

@@ -0,0 +1,283 @@
import { bytesToHex } from '@noble/hashes/utils';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
import { PubKey } from 'cosmjs-types/cosmos/crypto/ed25519/keys';
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing';
import { AuthInfo, SignDoc, TxBody } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx';
import { Any } from 'cosmjs-types/google/protobuf/any';
import Long from 'long';
import { MessageType } from '../message';
import type { ITxMsgBuilder } from '../ITxMsgBuilder';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
import type { SignerInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
function makeTxBodyBytes(body: Partial<TxBody>): Uint8Array {
return TxBody.encode(
TxBody.fromPartial({
...body,
}),
).finish();
}
export function decodeTxBody(txBody: Uint8Array): TxBody {
return TxBody.decode(txBody);
}
/**
* Create signer infos from the provided signers.
*
* This implementation does not support different signing modes for the different signers.
*/
function makeSignerInfos(
signers: ReadonlyArray<{ readonly pubkey: any; readonly sequence: Long }>,
signMode: SignMode,
): SignerInfo[] {
return signers.map(
({ pubkey, sequence }): SignerInfo => ({
publicKey: pubkey,
modeInfo: {
single: { mode: signMode },
},
sequence,
}),
);
}
/**
* Creates and serializes an AuthInfo document.
*
* This implementation does not support different signing modes for the different signers.
*/
function makeAuthInfoBytes(
signers: ReadonlyArray<{ readonly pubkey: any; readonly sequence: Long }>,
feeAmount: readonly Coin[],
gasLimit: Long,
signMode = SignMode.SIGN_MODE_DIRECT,
): Uint8Array {
const authInfo = {
signerInfos: makeSignerInfos(signers, signMode),
fee: {
amount: [...feeAmount],
gasLimit,
},
};
return AuthInfo.encode(AuthInfo.fromPartial(authInfo)).finish();
}
function encodePubkey(pubkey: Uint8Array): Any {
const pubkeyProto = PubKey.fromPartial({
key: pubkey,
});
return Any.fromPartial({
typeUrl: '/cosmos.crypto.secp256k1.PubKey',
value: Uint8Array.from(PubKey.encode(pubkeyProto).finish()),
});
}
function makeSignDoc(
bodyBytes: Uint8Array,
authInfoBytes: Uint8Array,
chainId: string,
accountNumber: Long,
): SignDoc {
return {
bodyBytes,
authInfoBytes,
chainId,
accountNumber,
};
}
export function makeSignBytes({
accountNumber,
authInfoBytes,
bodyBytes,
chainId,
}: SignDoc): Uint8Array {
const signDoc = SignDoc.fromPartial({
authInfoBytes,
bodyBytes,
chainId,
accountNumber,
});
return SignDoc.encode(signDoc).finish();
}
export function fastMakeSignDoc(
messages: Any[],
memo: string,
gasLimit: string,
feeAmount: string,
pubkey: Uint8Array,
mainCoinDenom: string,
chainId: string,
accountNumber: string,
nonce: string,
): SignDoc {
const bodyBytes = makeTxBodyBytes({
messages,
memo,
});
const encodePub = encodePubkey(pubkey);
const authBytes = makeAuthInfoBytes(
[{ pubkey: encodePub, sequence: Long.fromString(nonce) }],
[
{
amount: feeAmount,
denom: mainCoinDenom,
},
],
Long.fromString(gasLimit),
);
return makeSignDoc(
bodyBytes,
authBytes,
chainId,
Long.fromString(accountNumber),
);
}
export function makeMsgSend(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
): Any {
return {
typeUrl: MessageType.SEND,
value: MsgSend.encode(
MsgSend.fromPartial({
fromAddress,
toAddress,
amount: [
{
amount: value,
denom,
},
],
}),
).finish(),
};
}
function removeNull(obj: any): any {
if (obj !== null && typeof obj === 'object') {
return Object.entries(obj)
.filter(([, v]) => v != null)
.reduce(
(acc, [k, v]) => ({
...acc,
[k]: v === Object(v) && !Array.isArray(v) ? removeNull(v) : v,
}),
{},
);
}
return obj;
}
export function makeMsgExecuteContract(
sender: string,
contract: string,
msg: object,
funds?: Array<Coin>,
): Any {
return {
typeUrl: MessageType.EXECUTE_CONTRACT,
value: MsgExecuteContract.encode(
MsgExecuteContract.fromPartial({
sender,
contract,
msg: Buffer.from(JSON.stringify(removeNull(msg))),
funds,
}),
).finish(),
};
}
export function makeTerraMsgExecuteContract(
sender: string,
contract: string,
msg: object,
funds?: Array<Coin>,
): Any {
return {
typeUrl: MessageType.TERRA_EXECUTE_CONTRACT,
value: MsgExecuteContract.encode(
MsgExecuteContract.fromPartial({
sender,
contract,
msg: Buffer.from(JSON.stringify(removeNull(msg))),
funds,
}),
).finish(),
};
}
export class TxProtoMsgBuilder implements ITxMsgBuilder {
makeExecuteContractMsg(
sender: string,
contract: string,
msg: object,
funds?: Coin[] | undefined,
) {
const value = MsgExecuteContract.encode(
MsgExecuteContract.fromPartial({
sender,
contract,
msg: Buffer.from(JSON.stringify(removeNull(msg))),
funds,
}),
).finish();
return {
typeUrl: MessageType.EXECUTE_CONTRACT,
value: bytesToHex(value),
};
}
makeSendNativeMsg(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
) {
const valueU8 = MsgSend.encode(
MsgSend.fromPartial({
fromAddress,
toAddress,
amount: [
{
amount: value,
denom,
},
],
}),
).finish();
return {
typeUrl: MessageType.SEND,
value: bytesToHex(valueU8),
};
}
makeSendCwTokenMsg(
sender: string,
contract: string,
toAddress: string,
value: string,
) {
return this.makeExecuteContractMsg(
sender,
contract,
{
transfer: {
recipient: toAddress,
amount: value,
},
},
[],
);
}
}

View File

@@ -0,0 +1,90 @@
import { MsgGrant, MsgRevoke } from 'cosmjs-types/cosmos/authz/v1beta1/tx';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
import { MsgWithdrawDelegatorReward } from 'cosmjs-types/cosmos/distribution/v1beta1/tx';
import { MsgDeposit, MsgVote } from 'cosmjs-types/cosmos/gov/v1beta1/tx';
import {
MsgBeginRedelegate,
MsgDelegate,
MsgUndelegate,
} from 'cosmjs-types/cosmos/staking/v1beta1/tx';
import {
MsgExecuteContract,
MsgInstantiateContract,
} from 'cosmjs-types/cosmwasm/wasm/v1/tx';
import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx';
import { MessageType, UnknownMessage } from '../message';
import type { Any } from 'cosmjs-types/google/protobuf/any';
import type * as $protobuf from 'protobufjs';
export type UnpackedMessage =
| Any
| (Any & { unpacked: any; factory?: ProtoFactory });
interface ProtoFactory {
encode: (message: any, writer?: $protobuf.Writer) => $protobuf.Writer;
decode: (r: $protobuf.Reader | Uint8Array, l?: number) => any;
fromJSON: (object: any) => any;
toJSON: (message: any) => unknown;
}
export class ProtoDecode {
protected typeUrlFactoryMap: Map<string, ProtoFactory> = new Map();
unpackMessage(any: Any): UnpackedMessage {
const factory = this.typeUrlFactoryMap.get(any.typeUrl);
if (!factory) {
return new UnknownMessage(any.typeUrl, any.value);
}
const unpacked = factory.decode(any.value);
return {
...any,
unpacked,
factory,
};
}
registerFactory(typeUrl: string, message: ProtoFactory): void {
this.typeUrlFactoryMap.set(typeUrl, message);
}
}
export const defaultProtoDecodeRegistry = new ProtoDecode();
defaultProtoDecodeRegistry.registerFactory(MessageType.SEND, MsgSend);
defaultProtoDecodeRegistry.registerFactory(MessageType.DELEGATE, MsgDelegate);
defaultProtoDecodeRegistry.registerFactory(
MessageType.UNDELEGATE,
MsgUndelegate,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.REDELEGATE,
MsgBeginRedelegate,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.EXECUTE_CONTRACT,
MsgExecuteContract,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.TERRA_EXECUTE_CONTRACT,
MsgExecuteContract,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.INSTANTIATE_CONTRACT,
MsgInstantiateContract,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.WITHDRAW_DELEGATOR_REWARD,
MsgWithdrawDelegatorReward,
);
defaultProtoDecodeRegistry.registerFactory(
MessageType.IBC_TRANSFER,
MsgTransfer,
);
defaultProtoDecodeRegistry.registerFactory(MessageType.VOTE, MsgVote);
defaultProtoDecodeRegistry.registerFactory(MessageType.DEPOSIT, MsgDeposit);
defaultProtoDecodeRegistry.registerFactory(MessageType.GRANT, MsgGrant);
defaultProtoDecodeRegistry.registerFactory(MessageType.REVOKE, MsgRevoke);

View File

@@ -0,0 +1,114 @@
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { AuthInfo, SignDoc, TxBody } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import Long from 'long';
import { UnknownMessage } from '../message';
import { defaultProtoDecodeRegistry } from './protoDecode';
import type { ProtoDecode, UnpackedMessage } from './protoDecode';
import type { ICosmosSignDocHex } from '../../types';
export class ProtoSignDoc {
public static decode(bytes: Uint8Array): ProtoSignDoc {
const signDoc = SignDoc.decode(bytes);
return new ProtoSignDoc({
bodyBytes: bytesToHex(signDoc.bodyBytes),
authInfoBytes: bytesToHex(signDoc.authInfoBytes),
chainId: signDoc.chainId,
accountNumber: signDoc.accountNumber.toString(),
});
}
protected _txBody?: TxBody;
protected _authInfo?: AuthInfo;
public readonly signDoc: ICosmosSignDocHex;
protected readonly protoDecode: ProtoDecode;
constructor(
signDoc: ICosmosSignDocHex,
protoCodec: ProtoDecode = defaultProtoDecodeRegistry,
) {
this.signDoc = signDoc;
this.protoDecode = protoCodec;
}
get txBody(): TxBody {
if (!this._txBody) {
this._txBody = TxBody.decode(hexToBytes(this.signDoc.bodyBytes));
}
return this._txBody;
}
set txBody(txBody: TxBody) {
this._txBody = txBody;
this.signDoc.bodyBytes = bytesToHex(TxBody.encode(txBody).finish());
}
get txMsgs(): UnpackedMessage[] {
const msgs: UnpackedMessage[] = [];
for (const msg of this.txBody.messages) {
msgs.push(this.protoDecode.unpackMessage(msg));
}
return msgs;
}
get authInfo(): AuthInfo {
if (!this._authInfo) {
this._authInfo = AuthInfo.decode(hexToBytes(this.signDoc.authInfoBytes));
}
return this._authInfo;
}
set authInfo(authInfo: AuthInfo) {
this._authInfo = authInfo;
this.signDoc.authInfoBytes = bytesToHex(AuthInfo.encode(authInfo).finish());
}
get chainId(): string {
return this.signDoc.chainId;
}
get accountNumber(): string {
return this.signDoc.accountNumber.toString();
}
toBytes(): Uint8Array {
return SignDoc.encode({
bodyBytes: hexToBytes(this.signDoc.bodyBytes),
authInfoBytes: hexToBytes(this.signDoc.authInfoBytes),
chainId: this.signDoc.chainId,
accountNumber: Long.fromString(this.signDoc.accountNumber),
}).finish();
}
toJSON(): any {
return {
txBody: {
...(TxBody.toJSON(this.txBody) as any),
...{
messages: this.txMsgs.map((msg) => {
if (msg) {
if (msg instanceof UnknownMessage) {
return msg.toJSON();
}
if ('factory' in msg) {
return msg.factory?.toJSON(msg.unpacked);
}
}
return msg;
}),
},
},
authInfo: AuthInfo.toJSON(this.authInfo),
chainId: this.chainId,
accountNumber: this.accountNumber,
};
}
}

View File

@@ -0,0 +1,17 @@
import bech32 from 'bech32';
export const pubkeyToCosmosPublic = (pub: Buffer, hrp = 'cosmos') => {
const AminoSecp256k1PubkeyPrefix = Buffer.from('EB5AE987', 'hex');
const AminoSecp256k1PubkeyLength = Buffer.from('21', 'hex');
const pubBuf = Buffer.concat([
AminoSecp256k1PubkeyPrefix,
AminoSecp256k1PubkeyLength,
pub,
]);
return bech32.encode(`${hrp}pub`, bech32.toWords(pubBuf));
};
export interface Publickey {
'@type': string;
key: string;
}

View File

@@ -0,0 +1,103 @@
import BigNumber from 'bignumber.js';
import { get } from 'lodash';
import type {
Cw20AssetInfo,
Cw20TokenBalance,
IQuery,
QueryChainInfo,
} from './IQuery';
import type { AxiosInstance } from 'axios';
interface Cw20TokenInfoResponse {
'name': string;
'symbol': string;
'decimals': number;
'total_supply': string;
}
export class CosmwasmQuery implements IQuery {
//
// /**
// * deprecated
// * less than wasmd_0.24
// */
// public async queryContract(
// contractAddress: string,
// query: any,
// ): Promise<any> {
// const queryHex = Buffer.from(JSON.stringify(query), 'utf-8').toString(
// 'hex',
// );
// return this.axios
// .get<{ result: { smart: string } }>(
// `/wasm/contract/${contractAddress}/smart/${queryHex}?encoding=utf-8`,
// )
// .then((i) => Buffer.from(i.data.result.smart, 'base64').toString());
// }
async queryContract(
axios: AxiosInstance,
contractAddress: string,
query: any,
): Promise<any> {
const queryBase64 = Buffer.from(JSON.stringify(query), 'utf-8').toString(
'base64',
);
return axios
.get<{ data: unknown }>(
`/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryBase64}`,
)
.then((i) => i.data.data);
}
public async queryCw20TokenInfo(
chainInfo: QueryChainInfo,
contractAddressArray: string[],
): Promise<Cw20AssetInfo[]> {
const { axios } = chainInfo;
if (!axios) throw new Error('axios is not defined');
return Promise.all(
contractAddressArray.map((contractAddress) =>
this.queryContract(axios, contractAddress, {
token_info: {},
}).then((result: Cw20TokenInfoResponse) => ({
contractAddress,
name: result.name,
decimals: result.decimals,
symbol: result.symbol,
})),
),
);
}
public async queryCw20TokenBalance(
chainInfo: QueryChainInfo,
contractAddress: string,
address: string[],
): Promise<Cw20TokenBalance[]> {
const { axios } = chainInfo;
if (!axios) throw new Error('axios is not defined');
return Promise.all(
address.map((i) =>
this.queryContract(axios, contractAddress, {
balance: { address: i },
}).then((result) => {
let balance: BigNumber;
try {
balance = new BigNumber(get(result, 'balance', '0'));
} catch (error) {
balance = new BigNumber(0);
}
return {
address: result,
balance,
};
}),
),
);
}
}

View File

@@ -0,0 +1,62 @@
import { OnekeyNetwork } from '@onekeyhq/shared/src/config/networkIds';
import { CosmwasmQuery } from './CosmwasmQuery';
import { MintScanQuery } from './MintScanQuery';
import { SecretwasmQuery } from './SecretwasmQuery';
import type { AxiosInstance } from 'axios';
import type BigNumber from 'bignumber.js';
export interface Cw20AssetInfo {
contractAddress: string;
name: string;
decimals: number;
symbol: string;
}
export interface Cw20TokenBalance {
address: string;
balance: BigNumber;
}
export interface QueryChainInfo {
networkId: string;
axios?: AxiosInstance;
}
export interface IQuery {
queryCw20TokenInfo: (
chainInfo: QueryChainInfo,
contractAddressArray: string[],
) => Promise<Cw20AssetInfo[]>;
queryCw20TokenBalance: (
chainInfo: QueryChainInfo,
contractAddress: string,
address: string[],
) => Promise<Cw20TokenBalance[]>;
}
class QueryRegistry {
private readonly registryMap: Map<string, IQuery> = new Map();
public get(chainId: string): IQuery | undefined {
return this.registryMap.get(chainId);
}
public register(chainId: string, query: IQuery): void {
this.registryMap.set(chainId, query);
}
}
export const queryRegistry = new QueryRegistry();
const cosmwasmQuery = new CosmwasmQuery();
queryRegistry.register(OnekeyNetwork.juno, cosmwasmQuery);
// queryRegistry.register(OnekeyNetwork.terra, cosmwasmQuery); // terra2
queryRegistry.register(OnekeyNetwork.osmosis, cosmwasmQuery);
queryRegistry.register(OnekeyNetwork.secretnetwork, new SecretwasmQuery());
const mintScanQuery = new MintScanQuery();
queryRegistry.register(OnekeyNetwork.cosmoshub, mintScanQuery);
queryRegistry.register(OnekeyNetwork.akash, mintScanQuery);
queryRegistry.register(OnekeyNetwork.fetch, mintScanQuery);

View File

@@ -0,0 +1,159 @@
import Axios from 'axios';
import BigNumber from 'bignumber.js';
import { OnekeyNetwork } from '@onekeyhq/shared/src/config/networkIds';
import type {
Cw20TokenBalance,
Cw20AssetInfo as ICw20AssetInfo,
IQuery,
QueryChainInfo,
} from './IQuery';
import type {
AssetInfo,
ContractsInfo,
Cw20AssetInfo,
RelayerPaths,
Transaction,
} from './mintScanTypes';
import type { AxiosInstance } from 'axios';
const NetworkIDMinScanMap: Record<string, string> = {
[OnekeyNetwork.cryptoorgchain]: 'cryptoorg',
[OnekeyNetwork.cosmoshub]: 'cosmos',
[OnekeyNetwork.akash]: 'akash',
[OnekeyNetwork.fetch]: 'fetchai',
[OnekeyNetwork.juno]: 'juno',
[OnekeyNetwork.osmosis]: 'osmosis',
[OnekeyNetwork.secretnetwork]: 'secret',
// [OnekeyNetwork.injective]: 'injective',
};
export class MintScanQuery implements IQuery {
private readonly axios: AxiosInstance;
constructor() {
this.axios = Axios.create({
baseURL: 'https://api.mintscan.io',
timeout: 30 * 1000,
});
}
async fetchCw20TokenInfos(networkId: string): Promise<Cw20AssetInfo[]> {
const chain = NetworkIDMinScanMap[networkId];
if (!chain) return [];
try {
const resp = await this.axios.get<{ assets: Cw20AssetInfo[] }>(
`/v2/assets/${chain}/cw20`,
);
return resp.data.assets;
} catch (error) {
return [];
}
}
async queryCw20TokenInfo(
chainInfo: QueryChainInfo,
contractAddressArray: string[],
): Promise<ICw20AssetInfo[]> {
const { networkId } = chainInfo;
const cw20Tokens = await this.fetchCw20TokenInfos(networkId);
const contractsSet = contractAddressArray.reduce((acc, cur) => {
if (acc.has(cur)) return acc;
return acc.add(cur);
}, new Set<string>());
const tokens = cw20Tokens.reduce((acc, cur) => {
if (contractsSet.has(cur.contract_address)) {
acc.push({
contractAddress: cur.contract_address,
name: cur.denom,
decimals: cur.decimal,
symbol: cur.denom,
});
}
return acc;
}, [] as ICw20AssetInfo[]);
return tokens;
}
async queryCw20TokenBalance(
chainInfo: QueryChainInfo,
contractAddress: string,
address: string[],
): Promise<Cw20TokenBalance[]> {
const balance = address.reduce((acc, cur) => {
acc.push({
address: cur,
balance: new BigNumber('0'),
});
return acc;
}, [] as Cw20TokenBalance[]);
return Promise.resolve(balance);
}
async fetchAssertInfos(networkId: string): Promise<AssetInfo[]> {
const chain = NetworkIDMinScanMap[networkId];
if (!chain) return [];
try {
const resp = await this.axios.get<{ assets: AssetInfo[] }>(
`/v2/assets/${chain}`,
);
return resp.data.assets;
} catch (error) {
return [];
}
}
async fetchContractsInfo(
networkId: string,
contracts: string,
): Promise<ContractsInfo | undefined> {
try {
const chain = NetworkIDMinScanMap[networkId];
if (!chain) return;
const resp = await this.axios.get<{ contract: ContractsInfo }>(
`/v1/${chain}/wasm/contracts/${contracts}`,
);
return resp.data.contract;
} catch (error) {
// ignore
}
}
async fetchRelayerPaths(networkId: string) {
try {
const chain = NetworkIDMinScanMap[networkId];
if (!chain) return;
const resp = await this.axios.get<{ sendable: RelayerPaths[] }>(
`/v1/relayer/${chain}/paths`,
);
return resp.data.sendable;
} catch (error) {
// ignore
}
}
// https://api.mintscan.io/v1/cosmos/account/cosmos16ds6gn6rlzlz09qm9an725kp4shklyrq7hm0le/txs?limit=50&from=0
async fetchAccountTxs(
networkId: string,
address: string,
limit = 50,
from = 0,
) {
try {
const chain = NetworkIDMinScanMap[networkId];
if (!chain) return;
const resp = await this.axios.get<Transaction[]>(
`/v1/${chain}/account/${address}/txs?limit=${limit}&from=${from}`,
);
return resp.data;
} catch (error) {
// ignore
}
}
}

View File

@@ -0,0 +1,24 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type {
Cw20AssetInfo,
Cw20TokenBalance,
IQuery,
QueryChainInfo,
} from './IQuery';
export class SecretwasmQuery implements IQuery {
public queryCw20TokenInfo(
chainInfo: QueryChainInfo,
contractAddressArray: string[],
): Promise<Cw20AssetInfo[]> {
throw new Error('Not implemented');
}
public queryCw20TokenBalance(
chainInfo: QueryChainInfo,
contractAddress: string,
address: string[],
): Promise<Cw20TokenBalance[]> {
throw new Error('Not implemented');
}
}

View File

@@ -0,0 +1,199 @@
export interface RelayerPaths {
chain_id: string;
paths: Path[];
}
export interface Path {
channel_id: string;
port_id: PortID;
channel_state: ChannelState;
counter_party: {
channel_id: string;
port_id: string;
channel_state: ChannelState;
};
auth: boolean;
stats: {
current: Current;
past: Current;
};
created_at: Date | null;
}
export enum ChannelState {
StateClosed = 'STATE_CLOSED',
StateOpen = 'STATE_OPEN',
StateTryopen = 'STATE_TRYOPEN',
}
export enum PortID {
Icahost = 'icahost',
Transfer = 'transfer',
}
export interface Current {
tx_num: TxNum;
vol: TxNum;
}
export interface TxNum {
transfer: number | null;
receive: number | null;
}
export interface Cw20AssetInfo {
id: number;
chain: string;
contract_address: string;
denom: string;
decimal: number;
display: number;
logo: string;
default: boolean;
coingecko_id: string;
}
export interface ContractsInfo {
tx_hash: string;
code_id: number;
creator: string;
contract_address: string;
admin: string;
label: string;
funds: any[];
messages: {
name: string;
symbol: string;
decimals: number;
initial_balances: {
address: string;
amount: string;
}[];
marketing: {
marketing: string;
description: string;
logo: {
url: string;
};
project: string;
};
};
executed_count: number;
permission: null;
permitted_address: null;
contract: string;
version: string;
instantiated_at: Date;
last_executed_at: Date;
}
export interface AssetInfo {
chain: string;
denom: string;
type: Type;
base_denom: string;
base_type: Type;
dp_denom: string;
origin_chain: string;
decimal: number;
description?: string;
image: string;
coinGeckoId: string;
path?: string;
channel?: string;
port?: string;
counter_party?: {
channel: string;
port: string;
denom: string;
};
contract?: string;
}
export enum Type {
Erc20 = 'erc20',
Native = 'native',
Staking = 'staking',
Ibc = 'ibc',
Bridge = 'bridge',
}
export interface Transaction {
header: Header;
data: Data;
}
export interface Data {
height: string;
txhash: string;
codespace: string;
code: number;
data: string;
info: string;
gas_wanted: string;
gas_used: string;
tx: Tx;
timestamp: string;
}
export interface Tx {
'@type': string;
body: Body;
auth_info: AuthInfo;
signatures: string[];
}
export interface AuthInfo {
signer_infos: SignerInfo[];
fee: Fee;
}
export interface Fee {
amount: Amount[];
gas_limit: string;
payer: string;
granter: string;
}
export interface Amount {
denom: string;
amount: string;
}
export interface SignerInfo {
public_key: PublicKey;
mode_info: ModeInfo;
sequence: string;
}
export interface ModeInfo {
single: Single;
}
export interface Single {
mode: string;
}
export interface PublicKey {
'@type': string;
key: string;
}
export interface Body {
messages: Message[];
memo: string;
timeout_height: string;
extension_options: any[];
non_critical_extension_options: any[];
}
export interface Message {
'@type': string;
}
export interface Header {
id: number;
chain_id: string;
block_id: number;
timestamp: Date;
}

View File

@@ -0,0 +1,160 @@
import { hexToBytes } from '@noble/hashes/utils';
import { PubKey } from 'cosmjs-types/cosmos/crypto/ed25519/keys';
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing';
import {
AuthInfo,
Fee,
SignerInfo,
TxBody,
TxRaw,
} from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { Any } from 'cosmjs-types/google/protobuf/any';
import Long from 'long';
import { stripHexPrefix } from '@onekeyhq/shared/src/utils/hexUtils';
import {
getAminoSignDoc,
getDirectSignDoc,
sortedJsonByKeyStringify,
} from './wrapper/utils';
import type { ProtoMsgsOrWithAminoMsgs } from './ITxMsgBuilder';
import type { TransactionWrapper } from './wrapper';
export interface TxBuilder {
makeTxWrapper(
messages: ProtoMsgsOrWithAminoMsgs,
params: {
memo: string;
gasLimit: string;
feeAmount: string;
pubkey: Uint8Array;
mainCoinDenom: string;
chainId: string;
accountNumber: string;
nonce: string;
},
): TransactionWrapper;
}
export const generateSignBytes = (
encodedTx: TransactionWrapper,
): Uint8Array => {
if (encodedTx.mode === 'amino') {
const signDoc = getAminoSignDoc(encodedTx);
return Buffer.from(sortedJsonByKeyStringify(signDoc));
}
const directSignDoc = getDirectSignDoc(encodedTx);
return directSignDoc.toBytes();
};
/**
* Sign the transaction with the provided signature.
* @param encodedTx - TransactionWrapper
* @returns Uint8Array
*/
export const serializeTxForSignature = (encodedTx: TransactionWrapper) => {
if (encodedTx.mode === 'amino') {
const signDoc = getAminoSignDoc(encodedTx);
return Buffer.from(sortedJsonByKeyStringify(signDoc));
}
const directSignDoc = getDirectSignDoc(encodedTx);
return directSignDoc.toBytes();
};
export const deserializeTx = (txBytes: Uint8Array) => {
const txRaw = TxRaw.decode(txBytes);
const txBody = TxBody.decode(txRaw.bodyBytes);
const authInfo = AuthInfo.decode(txRaw.authInfoBytes);
return {
txBody,
authInfo,
signatures: txRaw.signatures,
};
};
/**
*
* @param signDoc - TransactionWrapper
* @param signature - Uint8Array[]
* @param publicKey - hex string
* @returns Uint8Array
*/
export const serializeSignedTx = ({
txWrapper: signDoc,
signature: { signatures, signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON },
publicKey: { pubKey, pubKeyType = '/cosmos.crypto.secp256k1.PubKey' },
}: {
txWrapper: TransactionWrapper;
signature: {
signatures: Uint8Array[];
signMode?: SignMode;
};
publicKey: {
pubKey: string;
pubKeyType?: string;
};
}): Uint8Array => {
if (signDoc.mode === 'amino') {
const content = getAminoSignDoc(signDoc);
const msgs = signDoc.msg?.protoMsgs;
const pubKeyAny = Any.fromPartial({
typeUrl: pubKeyType,
value: Uint8Array.from(
PubKey.encode(
PubKey.fromPartial({
key: hexToBytes(stripHexPrefix(pubKey)),
}),
).finish(),
),
});
return TxRaw.encode({
bodyBytes: TxBody.encode(
TxBody.fromPartial({
messages: msgs?.map((msg) => ({
typeUrl: msg.typeUrl,
value: hexToBytes(msg.value),
})),
memo: content.memo,
}),
).finish(),
authInfoBytes: AuthInfo.encode({
signerInfos: [
SignerInfo.fromPartial({
publicKey: pubKeyAny,
modeInfo: {
single: {
mode: signMode,
},
},
sequence: Long.fromString(content.sequence),
}),
],
fee: Fee.fromPartial({
amount: content.fee.amount.map((amount) => ({
amount: amount.amount,
denom: amount.denom,
})),
gasLimit: Long.fromString(content.fee.gas),
}),
}).finish(),
signatures,
}).finish();
}
const { bodyBytes, authInfoBytes } = getDirectSignDoc(signDoc).signDoc;
return TxRaw.encode(
TxRaw.fromPartial({
bodyBytes: hexToBytes(bodyBytes),
authInfoBytes: hexToBytes(authInfoBytes),
signatures,
}),
).finish();
};

View File

@@ -0,0 +1,75 @@
import { TxAminoMsgBuilder } from './amino/TxAminoMsgBuilder';
import { TxProtoMsgBuilder } from './proto/TxProtoMsgBuilder';
import type { StdMsg } from './amino/types';
import type {
AnyHex,
ITxMsgBuilder,
ProtoMsgsOrWithAminoMsgs,
} from './ITxMsgBuilder';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
export class TxMsgBuilder {
aminoMsgBuilder: ITxMsgBuilder = new TxAminoMsgBuilder();
protoMsgBuilder: ITxMsgBuilder = new TxProtoMsgBuilder();
private makeProtoMsgsOrWithAminoMsgs(
aminoMsgFunc: (...args: any[]) => AnyHex | StdMsg,
protoMsgFunc: (...args: any[]) => AnyHex | StdMsg,
...args: any[]
) {
return {
aminoMsgs: [aminoMsgFunc(...args) as StdMsg],
protoMsgs: [protoMsgFunc(...args) as AnyHex],
};
}
makeSendNativeMsg(
fromAddress: string,
toAddress: string,
value: string,
denom: string,
): ProtoMsgsOrWithAminoMsgs {
return this.makeProtoMsgsOrWithAminoMsgs(
this.aminoMsgBuilder.makeSendNativeMsg.bind(this.aminoMsgBuilder),
this.protoMsgBuilder.makeSendNativeMsg.bind(this.protoMsgBuilder),
fromAddress,
toAddress,
value,
denom,
);
}
makeSendCwTokenMsg(
sender: string,
contract: string,
toAddress: string,
value: string,
): ProtoMsgsOrWithAminoMsgs {
return this.makeProtoMsgsOrWithAminoMsgs(
this.aminoMsgBuilder.makeSendCwTokenMsg.bind(this.aminoMsgBuilder),
this.protoMsgBuilder.makeSendCwTokenMsg.bind(this.protoMsgBuilder),
sender,
contract,
toAddress,
value,
);
}
makeExecuteContractMsg(
sender: string,
contract: string,
msg: object,
funds?: Coin[] | undefined,
): ProtoMsgsOrWithAminoMsgs {
return this.makeProtoMsgsOrWithAminoMsgs(
this.aminoMsgBuilder.makeExecuteContractMsg.bind(this.aminoMsgBuilder),
this.protoMsgBuilder.makeExecuteContractMsg.bind(this.protoMsgBuilder),
sender,
contract,
msg,
funds,
);
}
}

View File

@@ -0,0 +1,63 @@
import { bytesToHex } from '@noble/hashes/utils';
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import type { ICosmosSignDocHex } from '../../types';
import type { StdSignDoc } from '../amino/types';
import type { ProtoMsgsOrWithAminoMsgs } from '../ITxMsgBuilder';
import type { ProtoSignDoc } from '../proto/protoSignDoc';
export class TransactionWrapper {
protected _protoSignDoc?: ProtoSignDoc;
public readonly mode: 'amino' | 'direct';
public readonly msg: ProtoMsgsOrWithAminoMsgs | undefined;
constructor(
public readonly signDoc: StdSignDoc | ICosmosSignDocHex,
msg?: ProtoMsgsOrWithAminoMsgs,
) {
if ('msgs' in signDoc) {
this.mode = 'amino';
} else {
this.mode = 'direct';
}
this.msg = msg;
}
static fromAminoSignDoc(
signDoc: StdSignDoc,
msg: ProtoMsgsOrWithAminoMsgs | undefined,
) {
return new TransactionWrapper(signDoc, msg);
}
static fromDirectSignDoc(signDoc: SignDoc, msg: ProtoMsgsOrWithAminoMsgs) {
const signDocHex: ICosmosSignDocHex = {
bodyBytes: bytesToHex(signDoc.bodyBytes),
authInfoBytes: bytesToHex(signDoc.authInfoBytes),
chainId: signDoc.chainId,
accountNumber: signDoc.accountNumber.toString(),
};
return new TransactionWrapper(signDocHex, msg);
}
static fromDirectSignDocHex(
signDoc: ICosmosSignDocHex,
msg: ProtoMsgsOrWithAminoMsgs | undefined,
) {
return new TransactionWrapper(signDoc, msg);
}
static fromDirectSignDocBytes(
signDocBytes: Uint8Array,
msg: ProtoMsgsOrWithAminoMsgs,
) {
const sign = SignDoc.decode(signDocBytes);
return TransactionWrapper.fromDirectSignDoc(sign, msg);
}
clone(): TransactionWrapper {
return new TransactionWrapper(this.signDoc);
}
}

View File

@@ -0,0 +1,274 @@
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { BigNumber } from 'bignumber.js';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
import { AuthInfo, TxBody } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { get } from 'lodash';
import Long from 'long';
import { defaultAminoDecodeRegistry } from '../amino/aminoDecode';
import { defaultAminoMsgOpts } from '../amino/types';
import { MessageType } from '../message';
import { ProtoSignDoc } from '../proto/protoSignDoc';
import type { TransactionWrapper } from '.';
import type { ICosmosStdFee } from '../../types';
import type { StdSignDoc } from '../amino/types';
import type { UnpackedMessage } from '../proto/protoDecode';
import type { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
export function getDirectSignDoc(tx: TransactionWrapper): ProtoSignDoc {
if (tx.mode === 'amino') {
throw new Error('Sign doc is encoded as Amino Json');
}
if ('msgs' in tx.signDoc) {
throw new Error('Sign doc is encoded as Amino Json');
}
return new ProtoSignDoc(tx.signDoc);
}
export function getAminoSignDoc(tx: TransactionWrapper): StdSignDoc {
if (tx.mode === 'direct') {
throw new Error('Sign doc is encoded as Protobuf');
}
if (!('msgs' in tx.signDoc)) {
throw new Error('Unexpected error');
}
return tx.signDoc;
}
export function getChainId(tx: TransactionWrapper) {
if (tx.mode === 'amino') {
return getAminoSignDoc(tx).chain_id;
}
return getDirectSignDoc(tx).chainId;
}
export function getMsgs(tx: TransactionWrapper): UnpackedMessage[] {
if (tx.mode === 'amino') {
return getAminoSignDoc(tx).msgs.map((msg) =>
defaultAminoDecodeRegistry.unpackMessage(msg),
);
}
return getDirectSignDoc(tx).txMsgs;
}
export function getMemo(signDoc: TransactionWrapper): string {
if (signDoc.mode === 'amino') {
return getAminoSignDoc(signDoc).memo;
}
return getDirectSignDoc(signDoc).txBody.memo;
}
export function getFeeAmount(signDoc: TransactionWrapper): readonly Coin[] {
if (signDoc.mode === 'amino') {
return getAminoSignDoc(signDoc).fee.amount;
}
const fees: Coin[] = [];
for (const coinObj of getDirectSignDoc(signDoc).authInfo.fee?.amount ?? []) {
if (coinObj.denom == null || coinObj.amount == null) {
throw new Error('Invalid fee');
}
fees.push({
denom: coinObj.denom,
amount: coinObj.amount,
});
}
return fees;
}
export function getFee(signDoc: TransactionWrapper): ICosmosStdFee {
if (signDoc.mode === 'amino') {
const { fee } = getAminoSignDoc(signDoc);
return {
amount: fee.amount,
gas_limit: fee.gas,
payer: '',
granter: '',
};
}
const { fee } = getDirectSignDoc(signDoc).authInfo;
return {
amount: fee ? [...fee.amount] : [],
gas_limit: fee?.gasLimit?.toString() ?? '0',
payer: fee?.payer ?? '',
granter: fee?.granter ?? '',
feePayer: fee?.granter ?? '',
};
}
export function setFee(signDoc: TransactionWrapper, fee: ICosmosStdFee) {
const newSignDoc = signDoc;
if (newSignDoc.mode === 'amino') {
const aminoSignDoc = getAminoSignDoc(newSignDoc);
aminoSignDoc.fee = {
amount: fee.amount,
gas: fee.gas_limit,
};
// @ts-expect-error
newSignDoc.signDoc.fee = aminoSignDoc.fee;
return newSignDoc;
}
const directSignDoc = getDirectSignDoc(newSignDoc);
directSignDoc.authInfo = {
...directSignDoc.authInfo,
fee: {
amount: fee.amount,
gasLimit: Long.fromString(fee.gas_limit),
payer: fee.payer ?? '',
granter: fee.granter ?? '',
},
};
// @ts-expect-error
newSignDoc.authInfoBytes = bytesToHex(
AuthInfo.encode(directSignDoc.authInfo).finish(),
);
return newSignDoc;
}
export function setSendAmount(tx: TransactionWrapper, amount: string) {
const newTx = tx;
const [aminoMsg] = newTx.msg?.aminoMsgs ?? [];
let aminoMsgValue;
if (aminoMsg) {
aminoMsgValue = [
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
denom: get(aminoMsg.value.amount, '[0].denom'),
amount: new BigNumber(amount).toFixed(),
},
];
if (newTx?.msg?.aminoMsgs[0]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
newTx.msg.aminoMsgs[0].value.amount = aminoMsgValue;
}
}
const [protoMsg] = newTx.msg?.protoMsgs ?? [];
let protoMsgValue;
if (protoMsg) {
if (protoMsg.typeUrl !== MessageType.SEND) {
throw new Error('Invalid message type');
}
const sendMsg = MsgSend.decode(hexToBytes(protoMsg.value));
protoMsgValue = MsgSend.encode({
...sendMsg,
amount: [
{
denom: sendMsg.amount[0].denom,
amount: new BigNumber(amount).toFixed(),
},
],
}).finish();
if (newTx?.msg?.protoMsgs[0]) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
newTx.msg.protoMsgs[0].value = bytesToHex(protoMsgValue);
}
}
if (newTx.mode === 'amino') {
const aminoSignDoc = getAminoSignDoc(newTx);
const msg = aminoSignDoc.msgs[0];
if (msg.type !== defaultAminoMsgOpts.send.native.type) {
throw new Error('Unexpected error');
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
msg.value.amount = aminoMsgValue;
// @ts-expect-error
newTx.signDoc.msgs = aminoSignDoc.msgs;
// // @ts-expect-error
// tx.msg?.aminoMsgs[0].value = msg.value;
// // @ts-expect-error
// tx.msg?.protoMsgs[0].value = msg.value;
return newTx;
}
const directSignDoc = getDirectSignDoc(newTx);
const msg = directSignDoc.txMsgs[0];
if (msg.typeUrl !== MessageType.SEND) {
throw new Error('Invalid message type');
}
directSignDoc.txBody = {
...directSignDoc.txBody,
messages: [
{
typeUrl: MessageType.SEND,
value: protoMsgValue ?? new Uint8Array(),
},
],
};
// @ts-expect-error
newTx.signDoc.bodyBytes = bytesToHex(
TxBody.encode(directSignDoc.txBody).finish(),
);
return newTx;
}
export function getGas(signDoc: TransactionWrapper): number {
if (signDoc.mode === 'amino') {
const limit = getAminoSignDoc(signDoc).fee.gas;
if (limit == null) {
return 0;
}
return parseInt(limit);
}
const directSignDoc = getDirectSignDoc(signDoc);
if (directSignDoc.authInfo?.fee?.gasLimit) {
return directSignDoc.authInfo.fee?.gasLimit.toNumber() ?? 0;
}
return 0;
}
export function getSequence(signDoc: TransactionWrapper): BigNumber {
if (signDoc.mode === 'amino') {
return new BigNumber(getAminoSignDoc(signDoc).sequence ?? '0');
}
const { signerInfos } = getDirectSignDoc(signDoc).authInfo;
return (
signerInfos
.map((s) => new BigNumber(s.sequence.toString()))
.sort((a, b) => b.comparedTo(a))[0] ?? new BigNumber(0)
);
}
function sortObjectByKey(obj: Record<string, any>): any {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return obj.map(sortObjectByKey);
}
const sortedKeys = Object.keys(obj).sort();
const result: Record<string, any> = {};
sortedKeys.forEach((key) => {
result[key] = sortObjectByKey(obj[key]);
});
return result;
}
export function sortedJsonByKeyStringify(obj: Record<string, any>): string {
return JSON.stringify(sortObjectByKey(obj));
}

View File

@@ -0,0 +1,24 @@
import type { TransactionWrapper } from './sdkCosmos';
export interface ICosmosCoin {
denom: string;
amount: string;
}
export interface ICosmosSignDocHex {
bodyBytes: string;
authInfoBytes: string;
chainId: string;
accountNumber: string;
}
export interface ICosmosStdFee {
amount: ICosmosCoin[];
gas_limit: string;
payer: string;
granter: string;
feePayer?: string;
}
export type IEncodedTxCosmos = TransactionWrapper;

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,110 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'doge',
chainId: '0',
networkId: 'doge--0',
networkImpl: 'doge',
isTestnet: false,
},
hdAccountTemplate: "m/44'/3'/$$INDEX$$'/0/0",
hdAccounts: [
{
address: 'DTdKu8YgcxoXyjFCDtCeKimaZzsK27rcwT',
addresses: { '0/0': 'DTdKu8YgcxoXyjFCDtCeKimaZzsK27rcwT' },
path: "m/44'/3'/0'",
relPaths: ['0/0'],
xpub: 'dgub8sn8hvpMJJouWJh3GyJKLDmDfuRvAZLtHdJZXH78UnAau3ohH1SM7oeEWejBsWcDxuYWSojzAzxv6sf8aHrU6Z9isjRQ4sgTGWTVfw2KF87',
xpvtRaw:
'02fac39803db6d8c2f80000000794332fa322b20ef9f7d2e15e1a6eedb713971a2e0e09f7a8a915a77929f7c960040a03bcec86d90c9b24ff3a6144cd431b04b25c007721bdadcdae2eb58121973',
publicKey:
'028df5c5ff4af74130220bc00d3ec5a6c08a587f7bf9038be679e47980a63ff871',
privateKeyRaw:
'bcac54f1b5e7addea9a0eb4876095477975fdff397a37fc133c713d2782b20b2',
},
],
txSamples: [
{
encodedTx: '',
signedTx: {
'txid': '',
'rawTx': '',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/doge/CoreChainSoftware.test.ts
describe('DOGE Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it.skip('signTransaction', async () => {
const coreApi = new CoreChainHd();
// TODO doge tx mock
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Psbt } from 'bitcoinjs-lib';
import CoreChainSoftwareBtc from '../btc/CoreChainSoftware';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImportedBtc,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHdBtc,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ISignedTxPro,
} from '../../types';
import type { IBtcForkNetwork } from '../btc/types';
export default class CoreChainSoftware extends CoreChainSoftwareBtc {
override getPsbt({ network }: { network: IBtcForkNetwork }): Psbt {
return new Psbt({
network,
maximumFeeRate: 10000,
});
}
override signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
return super.signMessage(payload);
}
override signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
return super.signTransaction(payload);
}
override getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return super.getPrivateKeys(payload);
}
override getAddressFromPrivate(
query: ICoreApiGetAddressQueryImportedBtc,
): Promise<ICoreApiGetAddressItem> {
return super.getAddressFromPrivate(query);
}
override getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
return super.getAddressFromPublic(query);
}
override getAddressesFromHd(
query: ICoreApiGetAddressesQueryHdBtc,
): Promise<ICoreApiGetAddressesResult> {
return super.getAddressesFromHd(query);
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,116 @@
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'dot',
chainId: '0',
networkId: 'dot--polkadot',
networkImpl: 'dot',
isTestnet: false,
},
hdAccountTemplate: "m/44'/354'/$$INDEX$$'/0'/0'",
hdAccounts: [
{
// TODO use accountIdToAddress generate DOT real address
address: '',
addresses: {
// 12EKdsrFTWA3oZoEzoB4ZNh64VrkuLjFKDnxFpEJZx4JF2Y6
},
path: "m/44'/354'/0'/0'/0'",
publicKey:
'3665284cd84d9de003dac3389ac1d61b60039fe95f8b9de603dcc0ea4ecdec64',
privateKeyRaw:
'9800ef8f1df3e31aa14874f00a3a21c064e3d24d75b718c4b4c5ddc483661757',
},
],
txSamples: [
{
unsignedTx: {
inputs: [],
outputs: [],
payload: {},
encodedTx: '',
rawTxUnsigned:
'0504003665284cd84d9de003dac3389ac1d61b60039fe95f8b9de603dcc0ea4ecdec640065020000d62400001800000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3adb6a2726a6b98028731ef0c8a348880f377ec034eabb3a8aad7e04b49969ac9',
},
signedTx: {
txid: '',
rawTx: '',
signature:
'0x007806aea9f31e5b2317a3cb4266ff0e206b15a959fa4da2d861d9d26e134b279cf591ca099e3711c62df36f34d81ee69f08b20e764cf336b0d67a7f2b21580a09',
},
},
],
msgSamples: [],
});
// yarn jest packages/core/src/chains/dot/CoreChainSoftware.test.ts
describe('DOT Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
});
it.skip('signMessage', async () => {
const coreApi = new CoreChainHd();
// coreApi.signMessage
throw new Error('Method not implemented.');
});
});

View File

@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DOT Core tests signTransaction 1`] = `
{
"rawTx": "",
"signature": "0x007806aea9f31e5b2317a3cb4266ff0e206b15a959fa4da2d861d9d26e134b279cf591ca099e3711c62df36f34d81ee69f08b20e764cf336b0d67a7f2b21580a09",
"txid": "",
}
`;

View File

@@ -0,0 +1,219 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { bufferToU8a, u8aConcat, u8aToU8a, u8aWrapBytes } from '@polkadot/util';
import { hdLedger } from '@polkadot/util-crypto';
import { merge } from 'lodash';
import { OneKeyInternalError } from '@onekeyhq/shared/src/errors';
import { checkIsDefined } from '@onekeyhq/shared/src/utils/assertUtils';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import { addHexPrefix } from '@onekeyhq/shared/src/utils/hexUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import { mnemonicFromEntropy } from '../../secret';
import { encrypt } from '../../secret/encryptors/aes256';
import { slicePathTemplate } from '../../utils';
import { DOT_TYPE_PREFIX } from './types';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
} from '../../types';
const curve: ICurveName = 'ed25519';
const derivationHdLedger = (mnemonic: string, path: string) => {
try {
return hdLedger(mnemonic, path);
} catch (e: any) {
const { message }: { message: string } = e;
if (
message ===
'Expected a mnemonic with 24 words (or 25 including a password)'
) {
throw new OneKeyInternalError({
message,
key: 'msg__error_mnemonics_can_only_be_12_24',
});
}
throw e;
}
};
async function serializeMessage(message: string): Promise<Buffer> {
const encoded = u8aWrapBytes(message);
return Buffer.from(u8aToU8a(encoded));
}
export default class CoreChainSoftware extends CoreChainApiBase {
override async baseGetPrivateKeys({
payload,
}: {
payload: ICoreApiSignBasePayload;
}): Promise<ICoreApiPrivateKeysMap> {
const { credentials, account, password } = payload;
let privateKeys: ICoreApiPrivateKeysMap = {};
if (credentials.hd) {
const { relPaths } = account;
const pathComponents = account.path.split('/');
const usedRelativePaths = relPaths || [pathComponents.pop() as string];
const basePath = pathComponents.join('/');
const { seed, entropy } = credentials.hd;
const mnemonic = mnemonicFromEntropy(
bufferUtils.toBuffer(entropy),
password,
);
const keys = usedRelativePaths.map((relPath) => {
const path = `${basePath}/${relPath}`;
const keyPair = derivationHdLedger(mnemonic, path);
return {
path,
key: encrypt(password, Buffer.from(keyPair.secretKey.slice(0, 32))),
};
});
privateKeys = keys.reduce(
(ret, key) => ({ ...ret, [key.path]: bufferUtils.bytesToHex(key.key) }),
{},
);
}
if (credentials.imported) {
// TODO handle relPaths privateKey here
const { relPaths } = account;
const { privateKey } = credentials.imported;
privateKeys[account.path] = privateKey;
}
if (!Object.keys(privateKeys).length) {
throw new Error('No private keys found');
}
return privateKeys;
}
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
// throw new Error('Method not implemented.');
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
const txBytes = bufferUtils.toBuffer(
checkIsDefined(unsignedTx.rawTxUnsigned),
);
const [signature] = await signer.sign(txBytes);
const txSignature = u8aConcat(
DOT_TYPE_PREFIX.ed25519,
bufferToU8a(signature),
);
const txid = '';
const rawTx = ''; // build rawTx on highlevel which requires network
return {
txid,
rawTx,
signature: addHexPrefix(bufferUtils.bytesToHex(txSignature)),
};
}
override async signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
const { message } = payload.unsignedMsg;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
const wrapMessage = await serializeMessage(message);
const [signature] = await signer.sign(wrapMessage);
const txSignature = u8aConcat(
DOT_TYPE_PREFIX.ed25519,
bufferToU8a(signature),
);
return addHexPrefix(bufferUtils.bytesToHex(txSignature));
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
// throw new Error('Method not implemented.');
const { privateKeyRaw } = query;
const privateKey = bufferUtils.toBuffer(privateKeyRaw);
const pub = this.baseGetCurve(curve).publicFromPrivate(privateKey);
return this.getAddressFromPublic({
publicKey: bufferUtils.bytesToHex(pub),
networkInfo: query.networkInfo,
});
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
// throw new Error('Method not implemented.');
const { publicKey } = query;
return Promise.resolve({
address: '',
addresses: {},
publicKey,
});
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
const { template, hdCredential, password, indexes } = query;
const { entropy } = hdCredential;
const { pathPrefix, pathSuffix } = slicePathTemplate(template);
const indexFormatted = indexes.map((index) =>
pathSuffix.replace('{index}', index.toString()),
);
const mnemonic = mnemonicFromEntropy(
bufferUtils.toBuffer(entropy),
password,
);
const publicKeys = indexFormatted.map((index) => {
const path = `${pathPrefix}/${index}`;
const keyPair = derivationHdLedger(mnemonic, path);
return {
path,
pubkey: keyPair.publicKey,
};
});
if (publicKeys.length !== indexes.length) {
throw new OneKeyInternalError('Unable to get public key.');
}
const addresses = await Promise.all(
publicKeys.map(async (info) => {
const { path, pubkey } = info;
const publicKey = bufferUtils.bytesToHex(pubkey);
const result = await this.getAddressFromPublic({
publicKey,
networkInfo: query.networkInfo,
});
return merge({ publicKey, path }, result);
}),
);
return { addresses };
}
}

View File

@@ -0,0 +1,17 @@
import { CoreChainScopeBase } from '../../base/CoreChainScopeBase';
import type CoreChainHd from './CoreChainHd';
import type CoreChainImported from './CoreChainImported';
export default class extends CoreChainScopeBase {
override hd: CoreChainHd = this._createApiProxy('hd') as CoreChainHd;
protected override _hd = async () => (await import('./CoreChainHd')).default;
override imported: CoreChainImported = this._createApiProxy(
'imported',
) as CoreChainImported;
protected override _imported = async () =>
(await import('./CoreChainImported')).default;
}

View File

@@ -0,0 +1,6 @@
export const DOT_TYPE_PREFIX = {
ecdsa: new Uint8Array([2]),
ed25519: new Uint8Array([0]),
ethereum: new Uint8Array([2]),
sr25519: new Uint8Array([1]),
};

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainHd extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,5 @@
import CoreChainSoftware from './CoreChainSoftware';
export default class CoreChainImported extends CoreChainSoftware {
//
}

View File

@@ -0,0 +1,176 @@
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import coreTestsUtils from '../../../@tests/coreTestsUtils';
import coreTestsFixtures from '../../../@tests/fixtures/coreTestsFixtures';
import { EMessageTypesEth } from '../../types';
import CoreChainHd from './CoreChainHd';
const {
hdCredential,
// password,
networkInfo,
hdAccountTemplate,
hdAccounts,
txSamples,
msgSamples,
} = coreTestsFixtures.prepareCoreChainTestsFixtures({
networkInfo: {
networkChainCode: 'evm',
chainId: '1',
networkId: 'evm--1',
networkImpl: 'evm',
isTestnet: false,
},
hdAccountTemplate: "m/44'/60'/0'/0/$$INDEX$$",
hdAccounts: [
{
address: '0x1959f5f4979c5cd87d5cb75c678c770515cb5e0e',
path: "m/44'/60'/0'/0/0",
publicKey:
'02bd51e5b1a6e8271e1f87d2464b856790800c6c5fd38acdf1cee73857735fc8a4',
privateKeyRaw:
'105434ca932be16664cb5e44e5b006728577dd757440d068e6d15ef52c15a82f',
},
{
address: '0xefc840572b9889de6bf172da76b7fa59b53a0ea0',
path: "m/44'/60'/0'/0/1",
publicKey:
'03a94df05c696300de718a5a55000972733f00072a2abe9824ba7c91dae3b427b8',
privateKeyRaw:
'a9fe0d531d6059f699426fb4476352843549b59849d85354b0e00a61de8285fc',
},
],
txSamples: [
{
encodedTx: {
'chainId': 1,
'from': '0x1959f5f4979c5cd87d5cb75c678c770515cb5e0e',
'to': '0x1959f5f4979c5cd87d5cb75c678c770515cb5e0e',
'value': '0x0',
'data': '0x',
'customData': '0x',
'gas': '0x5208',
'gasLimit': '0x5208',
'maxFeePerGas': '0x2afd66d00',
'maxPriorityFeePerGas': '0x55d4a80',
'nonce': 2,
},
signedTx: {
'txid':
'0xe5c2d7a1a627352e918f3d7c239bba5173e98d11b023536c1bff3f235e874a71',
'rawTx':
'0x02f86b010284055d4a808502afd66d00825208941959f5f4979c5cd87d5cb75c678c770515cb5e0e8080c001a007a018ab9176aac61b58aa9876667694e02dacb817d3a5df6b993611078330e2a07bad91b357e89539f823c21aabcfc30bbe0d6e88de1a72fc2fc1c07dfe04feae',
},
},
{
encodedTx: {
'chainId': 1,
'from': '0x1959f5f4979c5cd87d5cb75c678c770515cb5e0e',
'to': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'value': '0x0',
'data':
'0xa9059cbb000000000000000000000000efc840572b9889de6bf172da76b7fa59b53a0ea00000000000000000000000000000000000000000000000000000000000000000',
'customData':
'0xa9059cbb000000000000000000000000efc840572b9889de6bf172da76b7fa59b53a0ea00000000000000000000000000000000000000000000000000000000000000000',
'gas': '0xcb32',
'gasLimit': '0xcb32',
'maxFeePerGas': '0x1908b1000',
'maxPriorityFeePerGas': '0x55d4a80',
'nonce': 2,
},
signedTx: {
'txid':
'0x296e582a7ed1437fcede237af47f7b522692d88b64200f97c7549d6b2532dc39',
'rawTx':
'0x02f8b0010284055d4a808501908b100082cb3294a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000efc840572b9889de6bf172da76b7fa59b53a0ea00000000000000000000000000000000000000000000000000000000000000000c080a0a3bdf7f73020ff452255e7f39c6660942dd3fbc979633a47e9cf28dadc847513a00b064649e8fff4882dbaab0409208d16760da72d3460dadcf3d4b3e29056df1a',
},
},
],
msgSamples: [
{
unsignedMsg: {
type: EMessageTypesEth.PERSONAL_SIGN,
message:
'0x4578616d706c652060706572736f6e616c5f7369676e60206d657373616765',
},
signedMsg:
'0xeb1610361c143bc8a80b296b6759477e3420909bcee38487cf8bbe75aff92edc76b35f7a2820a5a2668415803162d408dcbd6c3bd961d6cd09f38a0f33dc479c1c',
},
],
});
// yarn jest packages/core/src/chains/evm/CoreChainSoftware.test.ts
describe('EVM Core tests', () => {
it('mnemonic verify', () => {
coreTestsUtils.expectMnemonicValid({
hdCredential,
});
});
it('getAddressFromPublic', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPublicOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressFromPrivate', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromPrivateOk({
coreApi,
networkInfo,
hdAccounts,
});
});
it('getAddressesFromHd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetAddressFromHdOk({
coreApi,
networkInfo,
hdAccounts,
hdAccountTemplate,
hdCredential,
});
});
it('getPrivateKeys hd', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectGetPrivateKeysHdOk({
coreApi,
networkInfo,
hdAccounts,
hdCredential,
});
});
it('signTransaction', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignTransactionOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
txSamples,
});
// TODO not working
// const spy = jest.spyOn(coreApi, 'signTransaction');
// expect(spy).toHaveBeenCalled();
});
it('signMessage', async () => {
const coreApi = new CoreChainHd();
await coreTestsUtils.expectSignMessageOk({
coreApi,
networkInfo,
account: hdAccounts[0],
hdCredential,
msgSamples,
});
const unsignedMsg = msgSamples[0];
expect(
bufferUtils.hexToText(unsignedMsg.unsignedMsg.message, 'utf8'),
).toEqual('Example `personal_sign` message');
});
});

View File

@@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EVM Core tests signTransaction 1`] = `
{
"rawTx": "0x02f86b010284055d4a808502afd66d00825208941959f5f4979c5cd87d5cb75c678c770515cb5e0e8080c001a007a018ab9176aac61b58aa9876667694e02dacb817d3a5df6b993611078330e2a07bad91b357e89539f823c21aabcfc30bbe0d6e88de1a72fc2fc1c07dfe04feae",
"txid": "0xe5c2d7a1a627352e918f3d7c239bba5173e98d11b023536c1bff3f235e874a71",
}
`;
exports[`EVM Core tests signTransaction 2`] = `
{
"rawTx": "0x02f8b0010284055d4a808501908b100082cb3294a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000efc840572b9889de6bf172da76b7fa59b53a0ea00000000000000000000000000000000000000000000000000000000000000000c080a0a3bdf7f73020ff452255e7f39c6660942dd3fbc979633a47e9cf28dadc847513a00b064649e8fff4882dbaab0409208d16760da72d3460dadcf3d4b3e29056df1a",
"txid": "0x296e582a7ed1437fcede237af47f7b522692d88b64200f97c7549d6b2532dc39",
}
`;

View File

@@ -0,0 +1,151 @@
// TODO move to core
import { hexZeroPad, splitSignature } from '@ethersproject/bytes';
import { keccak256 } from '@ethersproject/keccak256';
import { serialize } from '@ethersproject/transactions';
import * as ethUtil from 'ethereumjs-util';
import { isString } from 'lodash';
import { uncompressPublicKey } from '@onekeyhq/core/src/secret';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import { CoreChainApiBase } from '../../base/CoreChainApiBase';
import { hashMessage } from './message';
import { getPublicKeyFromPrivateKey, packTransaction } from './sdkEvm';
import type { IEncodedTxEvm } from './types';
import type {
ICoreApiGetAddressItem,
ICoreApiGetAddressQueryImported,
ICoreApiGetAddressQueryPublicKey,
ICoreApiGetAddressesQueryHd,
ICoreApiGetAddressesResult,
ICoreApiPrivateKeysMap,
ICoreApiSignBasePayload,
ICoreApiSignMsgPayload,
ICoreApiSignTxPayload,
ICurveName,
ISignedTxPro,
IUnsignedMessageEth,
} from '../../types';
const curve: ICurveName = 'secp256k1';
export default abstract class CoreChainSoftware extends CoreChainApiBase {
override async getPrivateKeys(
payload: ICoreApiSignBasePayload,
): Promise<ICoreApiPrivateKeysMap> {
return this.baseGetPrivateKeys({
payload,
curve,
});
}
override async signTransaction(
payload: ICoreApiSignTxPayload,
): Promise<ISignedTxPro> {
const { unsignedTx } = payload;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
const tx = packTransaction(unsignedTx.encodedTx as IEncodedTxEvm);
const digest = keccak256(serialize(tx));
const [sig, recoveryParam] = await signer.sign(
Buffer.from(digest.slice(2), 'hex'),
);
const [r, s]: [Buffer, Buffer] = [sig.slice(0, 32), sig.slice(32)];
const signature = splitSignature({
recoveryParam,
r: hexZeroPad(`0x${r.toString('hex')}`, 32),
s: hexZeroPad(`0x${s.toString('hex')}`, 32),
});
const rawTx: string = serialize(tx, signature);
const txid: string = keccak256(rawTx);
return { txid, rawTx };
}
override async signMessage(payload: ICoreApiSignMsgPayload): Promise<string> {
const unsignedMsg = payload.unsignedMsg as IUnsignedMessageEth;
const signer = await this.baseGetSingleSigner({
payload,
curve,
});
let finalMessage: any = unsignedMsg.message;
if (isString(unsignedMsg.message)) {
// Special temporary fix for attribute name error on SpaceSwap
// https://onekeyhq.atlassian.net/browse/OK-18748
try {
const finalMessageParsed: {
message: { value1?: string; value?: string };
} = JSON.parse(unsignedMsg.message);
if (
finalMessageParsed?.message?.value1 !== undefined &&
finalMessageParsed?.message?.value === undefined &&
finalMessageParsed?.message
) {
finalMessageParsed.message.value =
finalMessageParsed?.message?.value1;
finalMessage = JSON.stringify(finalMessageParsed);
} else {
finalMessage = unsignedMsg.message;
}
} catch (e) {
finalMessage = unsignedMsg.message;
}
}
const messageHash = hashMessage({
messageType: unsignedMsg.type,
message: finalMessage,
});
const [sig, recId] = await signer.sign(ethUtil.toBuffer(messageHash));
const result = ethUtil.addHexPrefix(
Buffer.concat([sig, Buffer.from([recId + 27])]).toString('hex'),
);
return result;
}
override async getAddressFromPrivate(
query: ICoreApiGetAddressQueryImported,
): Promise<ICoreApiGetAddressItem> {
const { privateKeyRaw } = query;
const { publicKey } = await getPublicKeyFromPrivateKey({ privateKeyRaw });
const { address } = await this.getAddressFromPublic({
publicKey,
networkInfo: query.networkInfo,
});
return {
address,
publicKey,
};
}
override async getAddressFromPublic(
query: ICoreApiGetAddressQueryPublicKey,
): Promise<ICoreApiGetAddressItem> {
const { publicKey } = query;
const compressedPublicKey = bufferUtils.toBuffer(publicKey);
const uncompressedPublicKey = uncompressPublicKey(
curve,
compressedPublicKey,
);
const address = `0x${keccak256(uncompressedPublicKey.slice(-64)).slice(
-40,
)}`;
return Promise.resolve({ address, publicKey });
}
override async getAddressesFromHd(
query: ICoreApiGetAddressesQueryHd,
): Promise<ICoreApiGetAddressesResult> {
return this.baseGetAddressesFromHd(query, {
curve,
});
}
}

Some files were not shown because too many files have changed in this diff Show More