Skip to content

Commit 7d7ada7

Browse files
committed
add uniform wallet client
1 parent 5e58dbb commit 7d7ada7

15 files changed

Lines changed: 2087 additions & 11 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Logs
2+
logs
3+
*.log
4+
5+
node_modules
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@ping-pub/uniform-signing-client",
3+
"version": "0.0.25",
4+
"description": "Cosmos Multi-wallet Signing Client",
5+
"keywords": [
6+
"Cosmos",
7+
"Signing",
8+
"wallet"
9+
],
10+
"author": "liangping <18786721@qq.com>",
11+
"homepage": "https://github.com/ping-pub/tools",
12+
"license": "MIT",
13+
"main": "dist/index.js",
14+
"directories": {
15+
"lib": "dist",
16+
"test": "__tests__"
17+
},
18+
"publishConfig": {
19+
"access": "public"
20+
},
21+
"repository": {
22+
"type": "git",
23+
"url": "git+https://github.com/ping-pub/tools.git"
24+
},
25+
"scripts": {
26+
"build": "tsc",
27+
"test": "jest"
28+
},
29+
"type": "module",
30+
"bugs": {
31+
"url": "https://github.com/ping-pub/tools/issues"
32+
},
33+
"dependencies": {
34+
"@chain-registry/types": "^0.14.0",
35+
"cross-fetch": "^3.1.5",
36+
"@cosmjs/amino": "^0.30.1",
37+
"@cosmjs/cosmwasm-stargate": "^0.30.1",
38+
"@cosmjs/ledger-amino": "^0.30.1",
39+
"@cosmjs/proto-signing": "^0.30.1",
40+
"@cosmjs/stargate": "^0.30.1",
41+
"@ethersproject/hash": "5.7.0",
42+
"@ethersproject/signing-key": "5.7.0",
43+
"@ledgerhq/hw-transport-web-ble": "^6.27.13",
44+
"@ledgerhq/hw-transport-webusb": "^6.27.14",
45+
"@tharsis/transactions": "0.2.6",
46+
"@tharsis/eip712": "0.2.4",
47+
"@tharsis/proto": "0.1.20",
48+
"@metamask/eth-sig-util": "^5.1.0"
49+
},
50+
"devDependencies": {
51+
"@babel/core": "^7.20.12",
52+
"@babel/preset-env": "^7.20.2",
53+
"@babel/preset-typescript": "^7.18.6",
54+
"@types/bignumber.js": "^5.0.0",
55+
"@types/jest": "^29.2.5",
56+
"@types/w3c-web-usb": "1.0.6",
57+
"@types/ledgerhq__hw-transport": "4.21.4",
58+
"@types/node": "^18.11.18",
59+
"babel-jest": "^29.3.1",
60+
"jest": "^29.3.1",
61+
"jest-junit": "^15.0.0",
62+
"ts-node": "10.9.1"
63+
},
64+
"gitHead": "960b680796fb318dddd98ac5b3c755d677e80cbc"
65+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { EncodeObject } from "@cosmjs/proto-signing"
2+
import {
3+
createMsgWithdrawDelegatorReward,
4+
createIBCMsgTransfer,
5+
createMsgSend,
6+
createMsgBeginRedelegate,
7+
createMsgDelegate,
8+
createMsgUndelegate,
9+
createMsgVote,
10+
// createMsgWithdrawValidatorCommission,
11+
} from '@tharsis/proto'
12+
import {
13+
MSG_WITHDRAW_DELEGATOR_REWARD_TYPES,
14+
MSG_BEGIN_REDELEGATE_TYPES,
15+
MSG_DELEGATE_TYPES,
16+
MSG_SEND_TYPES,
17+
MSG_UNDELEGATE_TYPES,
18+
MSG_VOTE_TYPES,
19+
IBC_MSG_TRANSFER_TYPES,
20+
MSG_WITHDRAW_VALIDATOR_COMMISSION_TYPES,
21+
} from "@tharsis/eip712"
22+
23+
export interface MessageAdapter {
24+
toProto(message: EncodeObject): object
25+
getTypes: ()=> object
26+
}
27+
28+
export class WithdrawMessageAdapter implements MessageAdapter {
29+
toProto(message: EncodeObject) {
30+
const param = message.value
31+
return createMsgWithdrawDelegatorReward(param.delegatorAddress, param.validatorAddress)
32+
}
33+
getTypes() {
34+
return MSG_WITHDRAW_DELEGATOR_REWARD_TYPES
35+
}
36+
}
37+
38+
export class WithdrawCommissionMessageAdapter implements MessageAdapter {
39+
toProto(message: EncodeObject) {
40+
const param = message.value
41+
return createMsgWithdrawDelegatorReward(param.delegatorAddress, param.validatorAddress)
42+
}
43+
getTypes() {
44+
return MSG_WITHDRAW_VALIDATOR_COMMISSION_TYPES
45+
}
46+
}
47+
48+
export class BeginRedelegateMessageAdapter implements MessageAdapter {
49+
toProto(message: EncodeObject) {
50+
const param = message.value
51+
return createMsgBeginRedelegate(
52+
param.delegatorAddress,
53+
param.validatorSrcAddress,
54+
param.validatorDstAddress,
55+
param.amount.amount,
56+
param.amount.denom)
57+
}
58+
getTypes() {
59+
return MSG_BEGIN_REDELEGATE_TYPES
60+
}
61+
}
62+
63+
export class DelegateMessageAdapter implements MessageAdapter {
64+
toProto(message: EncodeObject) {
65+
const param = message.value
66+
const amount = Array.isArray(param.amount) ? param.amount[0] : param.amount
67+
return createMsgDelegate(param.delegatorAddress, param.validatorAddress, amount.amount, amount.denom)
68+
}
69+
getTypes() {
70+
return MSG_DELEGATE_TYPES
71+
}
72+
}
73+
74+
export class SendMessageAdapter implements MessageAdapter {
75+
toProto(message: EncodeObject) {
76+
const param = message.value
77+
const amount = Array.isArray(param.amount) ? param.amount[0] : param.amount
78+
return createMsgSend(param.fromAddress, param.toAddress, amount.amount, amount.denom)
79+
}
80+
getTypes() {
81+
return MSG_SEND_TYPES
82+
}
83+
}
84+
85+
export class UndelegateMessageAdapter implements MessageAdapter {
86+
toProto(message: EncodeObject) {
87+
const param = message.value
88+
const amount = Array.isArray(param.amount) ? param.amount[0] : param.amount
89+
return createMsgUndelegate(param.delegatorAddress, param.validatorAddress, amount.amount, amount.denom)
90+
}
91+
getTypes() {
92+
return MSG_UNDELEGATE_TYPES
93+
}
94+
}
95+
96+
export class VoteMessageAdapter implements MessageAdapter {
97+
toProto(message: EncodeObject) {
98+
const param = message.value
99+
return createMsgVote(param.proposalId, param.option, param.voter)
100+
}
101+
getTypes() {
102+
return MSG_VOTE_TYPES
103+
}
104+
}
105+
106+
export class IBCMessageAdapter implements MessageAdapter {
107+
toProto(message: EncodeObject) {
108+
const param = message.value
109+
return createIBCMsgTransfer(
110+
param.sourcePort,
111+
param.sourceChannel,
112+
param.amount.amount,
113+
param.amount.denom,
114+
param.sender,
115+
param.receiver,
116+
param.revisionNumber,
117+
param.revisionHeight,
118+
param.timeoutTimestamp
119+
)
120+
}
121+
getTypes() {
122+
return IBC_MSG_TRANSFER_TYPES
123+
}
124+
}
125+
126+
127+
export const defaultMessageAdapter: Record<string, MessageAdapter> = {
128+
"/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward": new WithdrawMessageAdapter(),
129+
"/cosmos.staking.v1beta1.MsgDelegate": new DelegateMessageAdapter(),
130+
"/cosmos.staking.v1beta1.MsgBeginRedelegate": new BeginRedelegateMessageAdapter(),
131+
"/cosmos.staking.v1beta1.MsgUndelegate": new UndelegateMessageAdapter(),
132+
"/cosmos.bank.v1beta1.MsgSend": new SendMessageAdapter(),
133+
"/cosmos.gov.v1beta1.MsgVote": new VoteMessageAdapter(),
134+
"/ibc.applications.transfer.v1.MsgTransfer": new IBCMessageAdapter(),
135+
"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission": new WithdrawCommissionMessageAdapter()
136+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { toBase64 } from "@cosmjs/encoding";
2+
import { Registry } from '@cosmjs/proto-signing'
3+
import { defaultRegistryTypes } from "@cosmjs/stargate";
4+
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
5+
import { AbstractWallet, WalletArgument, WalletName, createWallet } from "./Wallet";
6+
import { post } from "./utils/http";
7+
import { Transaction, TxResponse } from "./utils/type";
8+
import { wasmTypes } from "@cosmjs/cosmwasm-stargate/build/modules";
9+
import { makeAuthInfoBytes, TxBodyEncodeObject } from "@cosmjs/proto-signing/build";
10+
import { Any } from "cosmjs-types/google/protobuf/any";
11+
12+
export function isEthermint(chainId: string) {
13+
return chainId.search(/\w+_\d+-\d+/g) > -1
14+
}
15+
16+
export class UniClient {
17+
registry: Registry
18+
wallet: AbstractWallet
19+
constructor(name: WalletName, arg: WalletArgument) {
20+
this.registry = new Registry([...defaultRegistryTypes, ...wasmTypes])
21+
this.wallet = createWallet(name, arg, this.registry)
22+
}
23+
24+
async getAccounts() {
25+
return this.wallet.getAccounts()
26+
}
27+
28+
async sign(transaction: Transaction): Promise<TxRaw> {
29+
// const { signature, signed } = await this.wallet.sign(transaction);
30+
// return TxRaw.fromPartial({
31+
// bodyBytes: signed.bodyBytes,
32+
// authInfoBytes: signed.authInfoBytes,
33+
// signatures: [fromBase64(signature.signature)],
34+
// });
35+
return this.wallet.sign(transaction)
36+
}
37+
38+
async simulate(
39+
endpoint: string,
40+
transaction: Transaction
41+
) {
42+
43+
const pubkey = Any.fromPartial({
44+
typeUrl: '/cosmos.crypto.ed25519.PubKey',
45+
value: new Uint8Array()
46+
})
47+
const txBodyEncodeObject: TxBodyEncodeObject = {
48+
typeUrl: "/cosmos.tx.v1beta1.TxBody",
49+
value: {
50+
messages: transaction.messages,
51+
memo: transaction.memo,
52+
},
53+
};
54+
55+
const txBodyBytes = this.registry.encode(txBodyEncodeObject);
56+
const gasLimit = Number(transaction.fee.gas);
57+
const authInfoBytes = makeAuthInfoBytes(
58+
[{ pubkey, sequence: transaction.signerData.sequence }],
59+
transaction.fee.amount,
60+
gasLimit,
61+
transaction.fee.granter,
62+
transaction.fee.payer,
63+
);
64+
65+
const txRaw = TxRaw.fromPartial({
66+
bodyBytes: txBodyBytes,
67+
authInfoBytes: authInfoBytes,
68+
signatures: [new Uint8Array()],
69+
});
70+
71+
const txbytes = toBase64(TxRaw.encode(txRaw).finish())
72+
const request = {
73+
tx_bytes: txbytes,
74+
mode: 'BROADCAST_MODE_SYNC', // BROADCAST_MODE_SYNC, BROADCAST_MODE_BLOCK, BROADCAST_MODE_ASYNC
75+
}
76+
return post(`${endpoint}/cosmos/tx/v1beta1/simulate`, request).then(res => {
77+
if (res.code && res.code !== 0) {
78+
throw new Error(res.message)
79+
}
80+
if (res.tx_response && res.tx_response.code !== 0) {
81+
throw new Error(res.tx_response.raw_log)
82+
}
83+
return Number(res.gas_info.gas_used)
84+
})
85+
}
86+
87+
async broadcastTx(endpoint, bodyBytes: TxRaw): Promise<{ tx_response: TxResponse }> {
88+
// const txbytes = bodyBytes.authInfoBytes ? TxRaw.encode(bodyBytes).finish() : bodyBytes
89+
const txbytes = TxRaw.encode(bodyBytes).finish()
90+
const txString = toBase64(txbytes)
91+
const txRaw = {
92+
tx_bytes: txString,
93+
mode: 'BROADCAST_MODE_SYNC', // BROADCAST_MODE_SYNC, BROADCAST_MODE_BLOCK, BROADCAST_MODE_ASYNC
94+
}
95+
return post(`${endpoint}/cosmos/tx/v1beta1/txs`, txRaw).then(res => {
96+
if (res.code && res.code !== 0) {
97+
throw new Error(res.message)
98+
}
99+
if (res.tx_response && res.tx_response.code !== 0) {
100+
throw new Error(res.tx_response.raw_log)
101+
}
102+
return res
103+
})
104+
}
105+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Registry } from '@cosmjs/proto-signing'
2+
import { defaultRegistryTypes } from "@cosmjs/stargate";
3+
import { Transaction } from "./utils/type";
4+
import { KeplerWallet } from './wallets/KeplerWallet';
5+
import { LedgerWallet } from './wallets/LedgerWallet';
6+
import { MetamaskWallet } from './wallets/MetamaskWallet';
7+
8+
export enum WalletName {
9+
Keplr = "Keplr",
10+
Ledger = "LedgerUSB",
11+
LedgerBLE = "LedgerBLE",
12+
Metamask = "Metemask",
13+
}
14+
15+
export interface ConnectedWallet {
16+
wallet: WalletName,
17+
cosmosAddress: string
18+
hdPath?: string
19+
}
20+
21+
export interface Account {
22+
address: string,
23+
algo: string,
24+
pubkey: Uint8Array,
25+
}
26+
27+
export interface WalletArgument {
28+
chainId?: string,
29+
hdPath?: string,
30+
address?: string,
31+
name?: string,
32+
transport?: string
33+
prefix?: string,
34+
}
35+
36+
export interface AbstractWallet {
37+
name: WalletName
38+
/**
39+
* The the accounts from the wallet (addresses)
40+
*/
41+
getAccounts(): Promise<Account[]>
42+
supportCoinType(coinType?: string): Promise<boolean>
43+
sign(transaction: Transaction): Promise<any>
44+
}
45+
46+
export const DEFAULT_HDPATH = "m/44'/118/0'/0/0";
47+
48+
export function readWallet(hdPath?: string) {
49+
return JSON.parse(
50+
localStorage.getItem(hdPath || DEFAULT_HDPATH) || '{}'
51+
) as ConnectedWallet
52+
}
53+
export function writeWallet(connected: ConnectedWallet, hdPath?: string) {
54+
localStorage.setItem(hdPath || DEFAULT_HDPATH, JSON.stringify(connected))
55+
}
56+
57+
export function removeWallet(hdPath?: string) {
58+
localStorage.removeItem(hdPath || DEFAULT_HDPATH);
59+
}
60+
61+
export function extractChainId(chainId: string) {
62+
const start = chainId.indexOf('_')
63+
const end = chainId.indexOf('-')
64+
if (end > start && start > 0) {
65+
return Number(chainId.substring(start + 1, end))
66+
}
67+
return 0
68+
}
69+
70+
export function createWallet(name: WalletName, arg: WalletArgument, registry?: Registry): AbstractWallet {
71+
const reg = registry || new Registry(defaultRegistryTypes)
72+
switch (name) {
73+
case WalletName.Keplr:
74+
return new KeplerWallet(arg, reg)
75+
case WalletName.Ledger:
76+
return new LedgerWallet(arg, reg)
77+
case WalletName.Metamask:
78+
return new MetamaskWallet(arg, reg)
79+
}
80+
throw new Error("No wallet connected")
81+
}

0 commit comments

Comments
 (0)