From 369fc005a73ed336332a2768b058d6349bcb19c0 Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Fri, 22 Mar 2024 15:36:42 +0100 Subject: [PATCH] hack Wallet integration --- package.json | 1 + src/chains/ethereum.ts | 90 ++++++++++++++++++++++++------------------ src/chains/near.ts | 18 +++++++++ src/mpcContract.ts | 23 +++++++++++ src/types.ts | 9 ++++- yarn.lock | 62 ++++++++++++++++++++++++++++- 6 files changed, 163 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 34e63d6..2a6b78d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@near-js/accounts": "^1.0.4", "@near-js/crypto": "^1.2.1", "@near-js/keystores": "^0.0.9", + "@near-wallet-selector/core": "^8.9.5", "bn.js": "^5.2.1", "bs58check": "^3.0.1", "elliptic": "^6.5.5", diff --git a/src/chains/ethereum.ts b/src/chains/ethereum.ts index 1e02c49..6b9f59c 100644 --- a/src/chains/ethereum.ts +++ b/src/chains/ethereum.ts @@ -9,7 +9,12 @@ import { http, Hash, } from "viem"; -import { BaseTx, NearEthAdapterParams, TxPayload } from "../types"; +import { + BaseTx, + NearEthAdapterParams, + NearSignPayload, + TxPayload, +} from "../types"; import { queryGasPrice } from "../utils/gasPrice"; import { MultichainContract } from "../mpcContract"; import BN from "bn.js"; @@ -71,51 +76,29 @@ export class NearEthAdapter { nearGas ); - const signedTx = NearEthAdapter.reconstructSignature( - transaction, - big_r, - big_s, - this.sender - ); + const signedTx = this.reconstructSignature(transaction, big_r, big_s); console.log("Relaying signed tx to EVM..."); return this.relayTransaction(signedTx); } - /** - * Builds a complete, unsigned transaction (with nonce, gas estimates, current prices) - * and payload bytes in preparation to be relayed to Near MPC contract. - * - * @param {BaseTx} tx - Minimal transaction data to be signed by Near MPC and executed on EVM. - * @returns transacion and its bytes (the payload to be signed on Near) - */ - async createTxPayload(tx: BaseTx): Promise { - const transaction = await this.buildTransaction(tx); - console.log("Built Transaction", JSON.stringify(transaction)); - const payload = Array.from( - new Uint8Array(transaction.getHashedMessageToSign().slice().reverse()) + async getSignatureRequstPayload( + txData: BaseTx, + nearGas?: BN + ): Promise { + console.log("Creating Payload for sender:", this.sender); + const { payload } = await this.createTxPayload(txData); + console.log("Requesting signature from Near..."); + return this.mpcContract.buildSignatureRequestTx( + payload, + this.derivationPath, + nearGas ); - return { transaction, payload }; - } - - /** - * Relays signed transaction to Etherem mempool for execution. - * @param signedTx - Signed Ethereum transaction. - * @returns Transaction Hash of relayed transaction. - */ - async relayTransaction(signedTx: FeeMarketEIP1559Transaction): Promise { - const serializedTx = bytesToHex(signedTx.serialize()) as Hex; - const txHash = await this.client.sendRawTransaction({ - serializedTransaction: serializedTx, - }); - console.log(`Transaction Confirmed: ${this.scanUrl}/tx/${txHash}`); - return txHash; } - private static reconstructSignature = ( + reconstructSignature = ( transaction: FeeMarketEIP1559Transaction, big_r: string, - big_s: string, - sender: Address + big_s: string ): FeeMarketEIP1559Transaction => { const r = Buffer.from(big_r.substring(2), "hex"); const s = Buffer.from(big_s, "hex"); @@ -123,7 +106,8 @@ export class NearEthAdapter { const candidates = [0n, 1n].map((v) => transaction.addSignature(v, r, s)); const signature = candidates.find( (c) => - c.getSenderAddress().toString().toLowerCase() === sender.toLowerCase() + c.getSenderAddress().toString().toLowerCase() === + this.sender.toLowerCase() ); if (!signature) { @@ -133,6 +117,36 @@ export class NearEthAdapter { return signature; }; + /** + * Relays signed transaction to Etherem mempool for execution. + * @param signedTx - Signed Ethereum transaction. + * @returns Transaction Hash of relayed transaction. + */ + async relayTransaction(signedTx: FeeMarketEIP1559Transaction): Promise { + const serializedTx = bytesToHex(signedTx.serialize()) as Hex; + const txHash = await this.client.sendRawTransaction({ + serializedTransaction: serializedTx, + }); + console.log(`Transaction Confirmed: ${this.scanUrl}/tx/${txHash}`); + return txHash; + } + + /** + * Builds a complete, unsigned transaction (with nonce, gas estimates, current prices) + * and payload bytes in preparation to be relayed to Near MPC contract. + * + * @param {BaseTx} tx - Minimal transaction data to be signed by Near MPC and executed on EVM. + * @returns transacion and its bytes (the payload to be signed on Near) + */ + private async createTxPayload(tx: BaseTx): Promise { + const transaction = await this.buildTransaction(tx); + console.log("Built Transaction", JSON.stringify(transaction)); + const payload = Array.from( + new Uint8Array(transaction.getHashedMessageToSign().slice().reverse()) + ); + return { transaction, payload }; + } + private async buildTransaction( tx: BaseTx ): Promise { diff --git a/src/chains/near.ts b/src/chains/near.ts index 35838ed..12e2fd6 100644 --- a/src/chains/near.ts +++ b/src/chains/near.ts @@ -1,5 +1,6 @@ import BN from "bn.js"; import { keyStores, KeyPair, connect, Account } from "near-api-js"; +import { Wallet } from "@near-wallet-selector/core"; export const TGAS = new BN(1000000000000); export const NO_DEPOSIT = "0"; @@ -53,3 +54,20 @@ export const nearAccountFromKeyPair = async (config: { const account = await near.account(config.accountId); return account; }; + +/** Minimally sufficient Account instance to construct the signing contract instance. + * Can't be used to change methods. + */ +export const nearAccountFromWallet = async ( + wallet: Wallet, + network?: NearConfig +): Promise => { + const keyStore = new keyStores.InMemoryKeyStore(); + const near = await connect({ + ...(network || TESTNET_CONFIG), + keyStore, + }); + const accountId = (await wallet.getAccounts())[0].accountId; + const account = await near.account(accountId); + return account; +}; diff --git a/src/mpcContract.ts b/src/mpcContract.ts index d502ade..14b0792 100644 --- a/src/mpcContract.ts +++ b/src/mpcContract.ts @@ -7,6 +7,7 @@ import { } from "./utils/kdf"; import { NO_DEPOSIT, nearAccountFromEnv, TGAS } from "./chains/near"; import BN from "bn.js"; +import { NearSignPayload } from "./types"; interface ChangeMethodArgs { args: T; @@ -76,4 +77,26 @@ export class MultichainContract { }); return { big_r, big_s }; }; + + buildSignatureRequestTx = async ( + payload: number[], + path: string, + gas?: BN + ): Promise => { + return { + signerId: this.contract.account.accountId, + receiverId: this.contract.contractId, + actions: [ + { + type: "FunctionCall", + params: { + methodName: "sign", + args: { path, payload }, + gas: (gas || TGAS.muln(200)).toString(), + deposit: NO_DEPOSIT, + }, + }, + ], + }; + }; } diff --git a/src/types.ts b/src/types.ts index 64c0674..74826ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { FeeMarketEIP1559Transaction } from "@ethereumjs/tx"; import { Address, Hex } from "viem"; import { MultichainContract } from "./mpcContract"; +import { FunctionCallAction } from "@near-wallet-selector/core"; export interface BaseTx { /// Recipient of the transaction @@ -28,7 +29,7 @@ export interface EvmParams { } export interface NearParams { - // A instance of the NearMPC contract connected to the associated near account. + /// An instance of the NearMPC contract connected to the associated near account. mpcContract: MultichainContract; /// path used to generate ETH account from Near account (e.g. "ethereum,1") derivationPath?: string; @@ -43,3 +44,9 @@ export interface TxPayload { transaction: FeeMarketEIP1559Transaction; payload: number[]; } + +export interface NearSignPayload { + signerId: string; + receiverId: string; + actions: Array; +} diff --git a/yarn.lock b/yarn.lock index a01cbbd..1e426d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -787,6 +787,16 @@ bn.js "5.2.1" borsh "1.0.0" +"@near-wallet-selector/core@^8.9.5": + version "8.9.5" + resolved "https://registry.yarnpkg.com/@near-wallet-selector/core/-/core-8.9.5.tgz#15e49f17252ee4e54a1c9719b8c2b98fd71aae74" + integrity sha512-wJiCL8M7z6tkNMY5H4n63/SZCmlW0Z15H6R1biWgpRuMDlVjhQOzxrmQggb1jbK4nYkzXyARNKyPh2gcRUuS+w== + dependencies: + borsh "0.7.0" + events "3.3.0" + js-sha256 "0.9.0" + rxjs "7.8.1" + "@noble/curves@1.2.0", "@noble/curves@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -1350,6 +1360,13 @@ base-x@^2.0.1: dependencies: safe-buffer "^5.0.1" +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + base-x@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" @@ -1365,7 +1382,7 @@ bn.js@4.11.6: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== -bn.js@5.2.1, bn.js@^5.2.1: +bn.js@5.2.1, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -1375,6 +1392,15 @@ bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== +borsh@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== + dependencies: + bn.js "^5.2.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + borsh@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/borsh/-/borsh-1.0.0.tgz#b564c8cc8f7a91e3772b9aef9e07f62b84213c1f" @@ -1431,6 +1457,13 @@ bs58@4.0.0: dependencies: base-x "^2.0.1" +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + bs58@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" @@ -1862,6 +1895,11 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" +events@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2646,6 +2684,11 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" +js-sha256@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== + js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -3219,6 +3262,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -3386,6 +3436,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-encoding-utf-8@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -3466,6 +3521,11 @@ tslib@2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"