mirror of
https://github.com/alexgo-io/onekey-monorepo.git
synced 2026-01-12 16:53:12 +08:00
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:
@@ -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',
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -7,7 +7,6 @@ __generated__
|
||||
.idea/
|
||||
.vscode/
|
||||
.chat/
|
||||
packages/core/src/chains/_x/
|
||||
|
||||
dist
|
||||
build-electron
|
||||
|
||||
9
@types/@mymonero/mymonero-app-bridge.d.ts
vendored
Normal file
9
@types/@mymonero/mymonero-app-bridge.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
15
@types/@mymonero/mymonero-keyimage-cache.d.ts
vendored
Normal file
15
@types/@mymonero/mymonero-keyimage-cache.d.ts
vendored
Normal 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;
|
||||
}
|
||||
91
@types/@mymonero/mymonero-lws-client.d.ts
vendored
Normal file
91
@types/@mymonero/mymonero-lws-client.d.ts
vendored
Normal 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
215
@types/tronweb.d.ts
vendored
Normal 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;
|
||||
}
|
||||
@@ -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(
|
||||
$(`
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
// };
|
||||
|
||||
5
packages/core/src/chains/algo/CoreChainHd.ts
Normal file
5
packages/core/src/chains/algo/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/algo/CoreChainImported.ts
Normal file
5
packages/core/src/chains/algo/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
105
packages/core/src/chains/algo/CoreChainSoftware.test.ts
Normal file
105
packages/core/src/chains/algo/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
}
|
||||
`;
|
||||
106
packages/core/src/chains/algo/CoreChainSoftware.ts
Normal file
106
packages/core/src/chains/algo/CoreChainSoftware.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/algo/index.ts
Normal file
17
packages/core/src/chains/algo/index.ts
Normal 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;
|
||||
}
|
||||
4
packages/core/src/chains/algo/sdkAlgo/index.ts
Normal file
4
packages/core/src/chains/algo/sdkAlgo/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import sdk from './sdkAlgo';
|
||||
|
||||
export * from './sdkAlgo';
|
||||
export default sdk;
|
||||
12
packages/core/src/chains/algo/sdkAlgo/sdkAlgo.ts
Normal file
12
packages/core/src/chains/algo/sdkAlgo/sdkAlgo.ts
Normal 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;
|
||||
1
packages/core/src/chains/algo/types.ts
Normal file
1
packages/core/src/chains/algo/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type IEncodedTxAlgo = string; // Base64 encoded string
|
||||
5
packages/core/src/chains/apt/CoreChainHd.ts
Normal file
5
packages/core/src/chains/apt/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/apt/CoreChainImported.ts
Normal file
5
packages/core/src/chains/apt/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
142
packages/core/src/chains/apt/CoreChainSoftware.test.ts
Normal file
142
packages/core/src/chains/apt/CoreChainSoftware.test.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`APT Core tests signTransaction 1`] = `
|
||||
{
|
||||
"rawTx": "ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed600000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e73666572000220ede225b4c713e12fee5a33397834d332e7d49773e06dc9860d35c30e6a479ed60800e1f505000000000a000000000000006400000000000000b15627650000000002002099b4709ae159e666615bdb5e0f0eb1f3738c7815c9a0c2173a824856859f1ea44020e5d9f7029a7a292208288bf0c33fb42ff3d3755f5a3da7c279aa75c9ee131b9b3d5bc36e4e0165baa0947b93a9b52e0ccce1c2dc203f16a5fb3688ab5e1e07",
|
||||
"txid": "",
|
||||
}
|
||||
`;
|
||||
152
packages/core/src/chains/apt/CoreChainSoftware.ts
Normal file
152
packages/core/src/chains/apt/CoreChainSoftware.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/apt/index.ts
Normal file
17
packages/core/src/chains/apt/index.ts
Normal 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;
|
||||
}
|
||||
5
packages/core/src/chains/bch/CoreChainHd.ts
Normal file
5
packages/core/src/chains/bch/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/bch/CoreChainImported.ts
Normal file
5
packages/core/src/chains/bch/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
112
packages/core/src/chains/bch/CoreChainSoftware.test.ts
Normal file
112
packages/core/src/chains/bch/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
91
packages/core/src/chains/bch/CoreChainSoftware.ts
Normal file
91
packages/core/src/chains/bch/CoreChainSoftware.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/bch/index.ts
Normal file
17
packages/core/src/chains/bch/index.ts
Normal 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;
|
||||
}
|
||||
5
packages/core/src/chains/btc/CoreChainHd.ts
Normal file
5
packages/core/src/chains/btc/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/btc/CoreChainImported.ts
Normal file
5
packages/core/src/chains/btc/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
114
packages/core/src/chains/btc/CoreChainSoftware.test.ts
Normal file
114
packages/core/src/chains/btc/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
1017
packages/core/src/chains/btc/CoreChainSoftware.ts
Normal file
1017
packages/core/src/chains/btc/CoreChainSoftware.ts
Normal file
File diff suppressed because it is too large
Load Diff
17
packages/core/src/chains/btc/index.ts
Normal file
17
packages/core/src/chains/btc/index.ts
Normal 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;
|
||||
}
|
||||
149
packages/core/src/chains/btc/sdkBtc/index.ts
Normal file
149
packages/core/src/chains/btc/sdkBtc/index.ts
Normal 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;
|
||||
}
|
||||
192
packages/core/src/chains/btc/sdkBtc/networks.ts
Normal file
192
packages/core/src/chains/btc/sdkBtc/networks.ts
Normal 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;
|
||||
}
|
||||
31
packages/core/src/chains/btc/types.ts
Normal file
31
packages/core/src/chains/btc/types.ts
Normal 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;
|
||||
};
|
||||
5
packages/core/src/chains/cfx/CoreChainHd.ts
Normal file
5
packages/core/src/chains/cfx/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/cfx/CoreChainImported.ts
Normal file
5
packages/core/src/chains/cfx/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
122
packages/core/src/chains/cfx/CoreChainSoftware.test.ts
Normal file
122
packages/core/src/chains/cfx/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CFX Core tests signTransaction 1`] = `
|
||||
{
|
||||
"digest": "0x830f7330fe140d0515381cad22f7796b2e2f7a5c451641797f7bb4ee5aed7eeb",
|
||||
"rawTx": "0xf86de980843b9aca00825208941a8348d02ae925bb86e4a4ac031ec42c28a4b5dc80808404d712bf8204058001a0c57de20f6d4c8c3785e675cfc76a5fad0fcb6e80bb15edb020ee8c71dfb54b27a003158bbebc413d8ecbd69475f60d8b6b1156c18aeb46d4afd7028a96cdcedfa3",
|
||||
"txid": "0xf94d5db32cdfd3c644274c17003a975a2082eb4fffcea4900b5e8a9c10a11884",
|
||||
}
|
||||
`;
|
||||
101
packages/core/src/chains/cfx/CoreChainSoftware.ts
Normal file
101
packages/core/src/chains/cfx/CoreChainSoftware.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/cfx/index.ts
Normal file
17
packages/core/src/chains/cfx/index.ts
Normal 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;
|
||||
}
|
||||
4
packages/core/src/chains/cfx/sdkCfx/conflux.ts
Normal file
4
packages/core/src/chains/cfx/sdkCfx/conflux.ts
Normal 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');
|
||||
67
packages/core/src/chains/cfx/sdkCfx/index.ts
Normal file
67
packages/core/src/chains/cfx/sdkCfx/index.ts
Normal 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(),
|
||||
};
|
||||
}
|
||||
16
packages/core/src/chains/cfx/types.ts
Normal file
16
packages/core/src/chains/cfx/types.ts
Normal 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;
|
||||
};
|
||||
5
packages/core/src/chains/cosmos/CoreChainHd.ts
Normal file
5
packages/core/src/chains/cosmos/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/cosmos/CoreChainImported.ts
Normal file
5
packages/core/src/chains/cosmos/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
163
packages/core/src/chains/cosmos/CoreChainSoftware.test.ts
Normal file
163
packages/core/src/chains/cosmos/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
@@ -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": "",
|
||||
}
|
||||
`;
|
||||
124
packages/core/src/chains/cosmos/CoreChainSoftware.ts
Normal file
124
packages/core/src/chains/cosmos/CoreChainSoftware.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/cosmos/index.ts
Normal file
17
packages/core/src/chains/cosmos/index.ts
Normal 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;
|
||||
}
|
||||
46
packages/core/src/chains/cosmos/sdkCosmos/ITxMsgBuilder.ts
Normal file
46
packages/core/src/chains/cosmos/sdkCosmos/ITxMsgBuilder.ts
Normal 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;
|
||||
}
|
||||
84
packages/core/src/chains/cosmos/sdkCosmos/address.ts
Normal file
84
packages/core/src/chains/cosmos/sdkCosmos/address.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
},
|
||||
);
|
||||
98
packages/core/src/chains/cosmos/sdkCosmos/amino/types.ts
Normal file
98
packages/core/src/chains/cosmos/sdkCosmos/amino/types.ts
Normal 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;
|
||||
}
|
||||
13
packages/core/src/chains/cosmos/sdkCosmos/index.ts
Normal file
13
packages/core/src/chains/cosmos/sdkCosmos/index.ts
Normal 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';
|
||||
62
packages/core/src/chains/cosmos/sdkCosmos/message.ts
Normal file
62
packages/core/src/chains/cosmos/sdkCosmos/message.ts
Normal 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[];
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
114
packages/core/src/chains/cosmos/sdkCosmos/proto/protoSignDoc.ts
Normal file
114
packages/core/src/chains/cosmos/sdkCosmos/proto/protoSignDoc.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/cosmos/sdkCosmos/publickey.ts
Normal file
17
packages/core/src/chains/cosmos/sdkCosmos/publickey.ts
Normal 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;
|
||||
}
|
||||
103
packages/core/src/chains/cosmos/sdkCosmos/query/CosmwasmQuery.ts
Normal file
103
packages/core/src/chains/cosmos/sdkCosmos/query/CosmwasmQuery.ts
Normal 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,
|
||||
};
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
62
packages/core/src/chains/cosmos/sdkCosmos/query/IQuery.ts
Normal file
62
packages/core/src/chains/cosmos/sdkCosmos/query/IQuery.ts
Normal 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);
|
||||
159
packages/core/src/chains/cosmos/sdkCosmos/query/MintScanQuery.ts
Normal file
159
packages/core/src/chains/cosmos/sdkCosmos/query/MintScanQuery.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
199
packages/core/src/chains/cosmos/sdkCosmos/query/mintScanTypes.ts
Normal file
199
packages/core/src/chains/cosmos/sdkCosmos/query/mintScanTypes.ts
Normal 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;
|
||||
}
|
||||
160
packages/core/src/chains/cosmos/sdkCosmos/txBuilder.ts
Normal file
160
packages/core/src/chains/cosmos/sdkCosmos/txBuilder.ts
Normal 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();
|
||||
};
|
||||
75
packages/core/src/chains/cosmos/sdkCosmos/txMsgBuilder.ts
Normal file
75
packages/core/src/chains/cosmos/sdkCosmos/txMsgBuilder.ts
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
63
packages/core/src/chains/cosmos/sdkCosmos/wrapper/index.ts
Normal file
63
packages/core/src/chains/cosmos/sdkCosmos/wrapper/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
274
packages/core/src/chains/cosmos/sdkCosmos/wrapper/utils.ts
Normal file
274
packages/core/src/chains/cosmos/sdkCosmos/wrapper/utils.ts
Normal 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));
|
||||
}
|
||||
24
packages/core/src/chains/cosmos/types.ts
Normal file
24
packages/core/src/chains/cosmos/types.ts
Normal 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;
|
||||
5
packages/core/src/chains/doge/CoreChainHd.ts
Normal file
5
packages/core/src/chains/doge/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/doge/CoreChainImported.ts
Normal file
5
packages/core/src/chains/doge/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
110
packages/core/src/chains/doge/CoreChainSoftware.test.ts
Normal file
110
packages/core/src/chains/doge/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
62
packages/core/src/chains/doge/CoreChainSoftware.ts
Normal file
62
packages/core/src/chains/doge/CoreChainSoftware.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/doge/index.ts
Normal file
17
packages/core/src/chains/doge/index.ts
Normal 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;
|
||||
}
|
||||
5
packages/core/src/chains/dot/CoreChainHd.ts
Normal file
5
packages/core/src/chains/dot/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/dot/CoreChainImported.ts
Normal file
5
packages/core/src/chains/dot/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
116
packages/core/src/chains/dot/CoreChainSoftware.test.ts
Normal file
116
packages/core/src/chains/dot/CoreChainSoftware.test.ts
Normal 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.');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DOT Core tests signTransaction 1`] = `
|
||||
{
|
||||
"rawTx": "",
|
||||
"signature": "0x007806aea9f31e5b2317a3cb4266ff0e206b15a959fa4da2d861d9d26e134b279cf591ca099e3711c62df36f34d81ee69f08b20e764cf336b0d67a7f2b21580a09",
|
||||
"txid": "",
|
||||
}
|
||||
`;
|
||||
219
packages/core/src/chains/dot/CoreChainSoftware.ts
Normal file
219
packages/core/src/chains/dot/CoreChainSoftware.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
17
packages/core/src/chains/dot/index.ts
Normal file
17
packages/core/src/chains/dot/index.ts
Normal 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;
|
||||
}
|
||||
6
packages/core/src/chains/dot/types.ts
Normal file
6
packages/core/src/chains/dot/types.ts
Normal 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]),
|
||||
};
|
||||
5
packages/core/src/chains/evm/CoreChainHd.ts
Normal file
5
packages/core/src/chains/evm/CoreChainHd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainHd extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
5
packages/core/src/chains/evm/CoreChainImported.ts
Normal file
5
packages/core/src/chains/evm/CoreChainImported.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import CoreChainSoftware from './CoreChainSoftware';
|
||||
|
||||
export default class CoreChainImported extends CoreChainSoftware {
|
||||
//
|
||||
}
|
||||
176
packages/core/src/chains/evm/CoreChainSoftware.test.ts
Normal file
176
packages/core/src/chains/evm/CoreChainSoftware.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
}
|
||||
`;
|
||||
151
packages/core/src/chains/evm/CoreChainSoftware.ts
Normal file
151
packages/core/src/chains/evm/CoreChainSoftware.ts
Normal 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
Reference in New Issue
Block a user