Created
March 14, 2024 20:21
-
-
Save phips28/bf157476a3c798b09c3edffa8a149996 to your computer and use it in GitHub Desktop.
Sign Cosmos-EVM chains with Keplr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { SigningStargateClient } from '@cosmjs/stargate' | |
import { fromBase64, toBase64 } from '@cosmjs/encoding' | |
import { makeAuthInfoBytes, makeSignDoc } from '@cosmjs/proto-signing' | |
import { Int53 } from '@cosmjs/math' | |
import { Any } from 'cosmjs-types/google/protobuf/any' | |
import { PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys' | |
import { AuthInfo, Fee, Tx, TxBody, TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' | |
import { isDev } from '@/helpers/env' | |
import { OfflineDirectSigner } from '@cosmjs/proto-signing/build/signer' | |
import { StdFee } from '@cosmjs/amino' | |
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing' | |
import { SimulateRequest } from 'cosmjs-types/cosmos/tx/v1beta1/service' | |
import { EncodeObject } from '@cosmjs/proto-signing/build/registry' | |
import { Chain } from '@chain-registry/types' | |
export const evmChainIds = [ | |
'dymension_1100-1', | |
'evmos_9001-2', | |
'acre_9052-1', | |
'canto_7700-1', | |
'blockx_100-1', | |
] | |
interface ISignProps { | |
client: SigningStargateClient | |
signer: OfflineDirectSigner | |
chain: Chain | |
signerAddress: string | |
messages: EncodeObject[] | |
fee: StdFee | |
memo: string | |
} | |
interface ISimulateProps { | |
client: SigningStargateClient | |
signer: OfflineDirectSigner | |
chain: Chain | |
signerAddress: string | |
messages: EncodeObject[] | |
memo: string | |
} | |
interface IAccount { | |
account: { | |
'@type': '/cosmos.auth.v1beta1.BaseAccount' | |
address: string | |
pub_key: { | |
'@type': string | |
key: string | |
} | |
account_number: string | |
sequence: string | |
} | |
} | |
interface IEthAccount { | |
account: { | |
'@type': '/ethermint.types.v1.EthAccount' | |
base_account: { | |
address: string | |
pub_key: { | |
'@type': string | |
key: string | |
} | |
account_number: string | |
sequence: string | |
} | |
code_hash: string | |
} | |
} | |
export async function signEvmWithKeplr({ | |
client, // SigningStargateClient | |
signer, // keplr OfflineSigner | |
chain, | |
signerAddress, | |
messages, | |
fee, | |
memo, | |
}: ISignProps) { | |
// Query account info, because cosmjs doesn't support Evmos account | |
const accountRes = await fetch( | |
`https://rest.cosmos.directory/${chain.chain_name}/cosmos/auth/v1beta1/accounts/${signerAddress}` | |
) | |
isDev && console.log('signEvmWithKeplr accountRes', accountRes) | |
if (!accountRes.ok) { | |
throw new Error( | |
'Failed to retrieve account from signer: ' + (accountRes.statusText || accountRes.status) | |
) | |
} | |
const accountResJson: IAccount | IEthAccount = await accountRes.json() | |
isDev && console.log('signEvmWithKeplr account', accountResJson.account) | |
const sequence = BigInt( | |
'base_account' in accountResJson.account | |
? accountResJson.account.base_account.sequence | |
: accountResJson.account.sequence | |
) | |
const accountNumber = Number( | |
'base_account' in accountResJson.account | |
? accountResJson.account.base_account.account_number | |
: accountResJson.account.account_number | |
) | |
const accountFromSigner = (await signer.getAccounts()).find( | |
(account) => account.address === signerAddress | |
) | |
if (!accountFromSigner) { | |
throw new Error('Failed to retrieve account from signer') | |
} | |
const pubkeyBytes = accountFromSigner.pubkey | |
// Custom typeUrl for EVMOS | |
const pubk = Any.fromPartial({ | |
typeUrl: '/ethermint.crypto.v1.ethsecp256k1.PubKey', | |
value: PubKey.encode({ | |
key: pubkeyBytes, | |
}).finish(), | |
}) | |
const txBodyEncodeObject = { | |
typeUrl: '/cosmos.tx.v1beta1.TxBody', | |
value: { | |
messages, | |
memo, | |
}, | |
} | |
isDev && console.log('signEvmWithKeplr txBodyEncodeObject', txBodyEncodeObject) | |
isDev && console.log('signEvmWithKeplr fee', fee) | |
const txBodyBytes = client.registry.encode(txBodyEncodeObject) | |
const gasLimit = Int53.fromString(fee.gas).toNumber() | |
const authInfoBytes = makeAuthInfoBytes( | |
[{ pubkey: pubk, sequence }], | |
fee.amount, | |
gasLimit, | |
undefined, | |
undefined | |
) | |
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chain.chain_id, accountNumber) | |
isDev && console.log('signEvmWithKeplr signDoc', signDoc) | |
const { signature, signed } = await signer.signDirect(signerAddress, signDoc) | |
// returns txBytes for broadcast | |
return TxRaw.encode({ | |
bodyBytes: signed.bodyBytes, | |
authInfoBytes: signed.authInfoBytes, | |
signatures: [fromBase64(signature.signature)], | |
}).finish() | |
} | |
export async function simulateEvmWithKeplr({ | |
client, // SigningStargateClient | |
signer, // keplr OfflineSigner | |
chain, | |
signerAddress, | |
messages, | |
memo, | |
}: ISimulateProps): Promise<string> { | |
// Query account info, because cosmjs doesn't support Evmos account | |
const accountRes = await fetch( | |
`https://rest.cosmos.directory/${chain.chain_name}/cosmos/auth/v1beta1/accounts/${signerAddress}` | |
) | |
// { | |
// "account": { | |
// "@type": "/cosmos.auth.v1beta1.BaseAccount", | |
// "address": "dym..", | |
// "pub_key": { | |
// "@type": "/ethermint.crypto.v1.ethsecp256k1.PubKey", | |
// "key": "asdf" | |
// }, | |
// "account_number": "345797", | |
// "sequence": "3" | |
// } | |
// } | |
isDev && console.log('signEvmWithKeplr accountRes', accountRes) | |
if (!accountRes.ok) { | |
throw new Error( | |
'Failed to retrieve account from signer: ' + (accountRes.statusText || accountRes.status) | |
) | |
} | |
const accountResJson: IAccount | IEthAccount = await accountRes.json() | |
isDev && console.log('signEvmWithKeplr account', accountResJson.account) | |
const sequence = BigInt( | |
'base_account' in accountResJson.account | |
? accountResJson.account.base_account.sequence | |
: accountResJson.account.sequence | |
) | |
const accountFromSigner = (await signer.getAccounts()).find( | |
(account) => account.address === signerAddress | |
) | |
if (!accountFromSigner) { | |
throw new Error('Failed to retrieve account from signer') | |
} | |
const pubkeyBytes = accountFromSigner.pubkey | |
// Custom typeUrl for EVMOS | |
const pubk = Any.fromPartial({ | |
typeUrl: '/ethermint.crypto.v1.ethsecp256k1.PubKey', | |
value: PubKey.encode({ | |
key: pubkeyBytes, | |
}).finish(), | |
}) | |
const txBodyEncodeObject = { | |
typeUrl: '/cosmos.tx.v1beta1.TxBody', | |
value: { | |
messages: messages, | |
memo: memo, | |
}, | |
} | |
isDev && console.log('signEvmWithKeplr txBodyEncodeObject', txBodyEncodeObject) | |
const anyMsgs = messages.map((m) => client.registry.encodeAsAny(m)) | |
isDev && console.log('signEvmWithKeplr anyMsgs', anyMsgs) | |
const tx = Tx.fromPartial({ | |
authInfo: AuthInfo.fromPartial({ | |
fee: Fee.fromPartial({}), | |
signerInfos: [ | |
{ | |
publicKey: pubk, | |
sequence, | |
modeInfo: { single: { mode: SignMode.SIGN_MODE_UNSPECIFIED } }, | |
}, | |
], | |
}), | |
body: TxBody.fromPartial({ | |
messages: Array.from(anyMsgs), | |
memo: memo, | |
}), | |
signatures: [new Uint8Array()], | |
}) | |
const request = SimulateRequest.fromPartial({ | |
txBytes: Tx.encode(tx).finish(), | |
}) | |
isDev && console.log('signEvmWithKeplr toBase64(request.txBytes)', toBase64(request.txBytes)) | |
const res = await fetch( | |
`https://rest.cosmos.directory/${chain.chain_name}/cosmos/tx/v1beta1/simulate`, | |
{ | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ tx_bytes: toBase64(request.txBytes) }), | |
} | |
) | |
if (!res.ok) { | |
// TODO handle error json? | |
console.error('Failed to simulate transaction:', res.statusText) | |
throw new Error('Failed to simulate transaction: ' + (res.statusText || res.status)) | |
} | |
const data: { | |
gas_info: { | |
gas_used: string | |
gas_wanted: string | |
} | |
} = await res.json() | |
isDev && console.log('signEvmWithKeplr data', data) | |
return data.gas_info.gas_used | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment