From 550d5db97806dedd7c3fe7ee3a6b141ff02489ee Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 6 Nov 2024 10:55:07 +0100 Subject: [PATCH 1/3] Attempt MultiSign --- examples/send-eth.ts | 16 ++++++------ src/chains/ethereum.ts | 27 +++++++++++++++----- src/mpcContract.ts | 57 ++++++++++++++++++++++++++++++++++++++++++ src/utils/mock-sign.ts | 9 +++++++ 4 files changed, 95 insertions(+), 14 deletions(-) diff --git a/examples/send-eth.ts b/examples/send-eth.ts index 372eb23..3077161 100644 --- a/examples/send-eth.ts +++ b/examples/send-eth.ts @@ -1,16 +1,16 @@ import dotenv from "dotenv"; -import { SEPOLIA_CHAIN_ID, setupNearEthAdapter } from "./setup"; +import { setupNearEthAdapter } from "./setup"; dotenv.config(); const run = async (): Promise => { const evm = await setupNearEthAdapter(); - await evm.signAndSendTransaction({ - // Sending to self. - to: evm.address, - // THIS IS ONE WEI! - value: 1n, - chainId: SEPOLIA_CHAIN_ID, - }); + console.log(evm.address); + // throw new Error("You foked up"); + const transactions = [ + { to: evm.address, value: 1n, chainId: 97 }, + { to: evm.address, value: 1n, chainId: 1301 }, + ]; + await evm.signAndSendTransaction(transactions); }; run(); diff --git a/src/chains/ethereum.ts b/src/chains/ethereum.ts index bfca8ae..66795c9 100644 --- a/src/chains/ethereum.ts +++ b/src/chains/ethereum.ts @@ -32,6 +32,7 @@ export class NearEthAdapter { readonly mpcContract: IMpcContract; readonly address: Address; readonly derivationPath: string; + readonly keyVersion: number; readonly beta: Beta; private constructor(config: { @@ -41,6 +42,7 @@ export class NearEthAdapter { }) { this.mpcContract = config.mpcContract; this.derivationPath = config.derivationPath; + this.keyVersion = 0; this.address = config.sender; this.beta = new Beta(this); } @@ -103,16 +105,29 @@ export class NearEthAdapter { * Note that the signature request is a recursive function. */ async signAndSendTransaction( - txData: BaseTx, + txData: BaseTx[], nearGas?: bigint - ): Promise { - const { transaction, signArgs } = await this.createTxPayload(txData); + ): Promise { + // Note that chainIds must be all different or + // we have to make special consideration for the nonces. + const txArray = await Promise.all( + txData.map((tx) => this.createTxPayload(tx)) + ); + console.log(`Requesting signature from ${this.mpcContract.accountId()}`); - const signature = await this.mpcContract.requestSignature( - signArgs, + const signatures = await this.mpcContract.requestMulti( + txArray.map((tx) => tx.signArgs), nearGas ); - return broadcastSignedTransaction({ transaction, signature }); + const transactions = txArray.map((tx) => tx.transaction); + return Promise.all( + transactions.map((transaction, i) => + broadcastSignedTransaction({ + transaction, + signature: signatures[i]!, + }) + ) + ); } /** diff --git a/src/mpcContract.ts b/src/mpcContract.ts index b264c54..0016db6 100644 --- a/src/mpcContract.ts +++ b/src/mpcContract.ts @@ -113,6 +113,34 @@ export class MpcContract implements IMpcContract { return transformSignature(mpcSig); }; + requestMulti = async ( + signArgs: SignArgs[], + gas?: bigint + ): Promise => { + const transaction = await this.encodeMulti(signArgs, gas); + + const result = await this.connectedAccount.signAndSendTransaction({ + receiverId: transaction.receiverId, + actions: transaction.actions.map((action) => { + return { + enum: action.type, + functionCall: { + methodName: action.params.methodName, + args: Buffer.from(JSON.stringify(action.params.args)), + gas: BigInt(action.params.gas), + deposit: BigInt(action.params.deposit), + }, + }; + }), + }); + + // Extract signatures from each receipt outcome in order + const mpcSigs = result.receipts_outcome.map( + (receipt) => receipt.outcome.status as MPCSignature + ); + return mpcSigs.map(transformSignature); + }; + async encodeSignatureRequestTx( signArgs: SignArgs, gas?: bigint @@ -133,6 +161,30 @@ export class MpcContract implements IMpcContract { ], }; } + + async encodeMulti( + signArgs: SignArgs[], + gas?: bigint + ): Promise> { + const deposit = await this.getDeposit(); + return { + signerId: this.connectedAccount.accountId, + receiverId: this.contract.contractId, + actions: signArgs.map((args) => { + return { + type: "FunctionCall", + params: { + methodName: "sign", + args: { + request: args, + }, + gas: gasOrDefault(gas), + deposit, + }, + }; + }), + }; + } } function gasOrDefault(gas?: bigint): string { @@ -149,8 +201,13 @@ export interface IMpcContract { deriveEthAddress(derivationPath: string): Promise
; getDeposit(): Promise; requestSignature(signArgs: SignArgs, gas?: bigint): Promise; + requestMulti(signArgs: SignArgs[], gas?: bigint): Promise; encodeSignatureRequestTx( signArgs: SignArgs, gas?: bigint ): Promise>; + encodeMulti( + signArgs: SignArgs[], + gas?: bigint + ): Promise>; } diff --git a/src/utils/mock-sign.ts b/src/utils/mock-sign.ts index 5ce06e9..e52ec4b 100644 --- a/src/utils/mock-sign.ts +++ b/src/utils/mock-sign.ts @@ -48,6 +48,15 @@ export class MockMpcContract implements IMpcContract { "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ); } + requestMulti(_signArgs: SignArgs[], _gas?: bigint): Promise { + throw new Error("Method not implemented."); + } + encodeMulti( + _signArgs: SignArgs[], + _gas?: bigint + ): Promise> { + throw new Error("Method not implemented."); + } accountId(): string { return "mock-mpc.offline"; From 44f16b625f07dd47a6e14b68bf2ecb6e8789dae3 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 6 Nov 2024 11:14:59 +0100 Subject: [PATCH 2/3] fix send --- src/mpcContract.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/mpcContract.ts b/src/mpcContract.ts index 0016db6..501f659 100644 --- a/src/mpcContract.ts +++ b/src/mpcContract.ts @@ -8,6 +8,7 @@ import { import { TGAS } from "./chains/near"; import { MPCSignature, FunctionCallTransaction, SignArgs } from "./types"; import { transformSignature } from "./utils/signature"; +import { Action, FunctionCall } from "near-api-js/lib/transaction"; /** * Near Contract Type for change methods. @@ -118,19 +119,19 @@ export class MpcContract implements IMpcContract { gas?: bigint ): Promise => { const transaction = await this.encodeMulti(signArgs, gas); - + // TODO: This is a hack to prevent out of gas errors + const maxGasPerAction = 300000000000000n / BigInt(signArgs.length); const result = await this.connectedAccount.signAndSendTransaction({ receiverId: transaction.receiverId, actions: transaction.actions.map((action) => { - return { - enum: action.type, - functionCall: { - methodName: action.params.methodName, + return new Action({ + functionCall: new FunctionCall({ + methodName: "sign", args: Buffer.from(JSON.stringify(action.params.args)), - gas: BigInt(action.params.gas), + gas: maxGasPerAction, deposit: BigInt(action.params.deposit), - }, - }; + }), + }); }), }); From d2606b22928e3a847919f6625a71719dfc85786b Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Wed, 6 Nov 2024 11:22:39 +0100 Subject: [PATCH 3/3] fix borsch issue --- tests/e2e.test.ts | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/tests/e2e.test.ts b/tests/e2e.test.ts index 49f9dad..64a102c 100644 --- a/tests/e2e.test.ts +++ b/tests/e2e.test.ts @@ -27,24 +27,28 @@ describe("End To End", () => { it.skip("signAndSendTransaction", async () => { await expect( - realAdapter.signAndSendTransaction({ - // Sending 1 WEI to self (so we never run out of funds) - to: realAdapter.address, - value: ONE_WEI, - chainId, - }) + realAdapter.signAndSendTransaction([ + { + // Sending 1 WEI to self (so we never run out of funds) + to: realAdapter.address, + value: ONE_WEI, + chainId, + }, + ]) ).resolves.not.toThrow(); }); it.skip("signAndSendTransaction - Gnosis Chain", async () => { await expect( - realAdapter.signAndSendTransaction({ - // Sending 1 WEI to self (so we ~never run out of funds) - to: realAdapter.address, - value: ONE_WEI, - // Gnosis Chain! - chainId: 100, - }) + realAdapter.signAndSendTransaction([ + { + // Sending 1 WEI to self (so we ~never run out of funds) + to: realAdapter.address, + value: ONE_WEI, + // Gnosis Chain! + chainId: 100, + }, + ]) ).resolves.not.toThrow(); }); @@ -54,11 +58,13 @@ describe("End To End", () => { address: mockedAdapter.address, }); await expect( - realAdapter.signAndSendTransaction({ - to, - value: senderBalance + ONE_WEI, - chainId, - }) + realAdapter.signAndSendTransaction([ + { + to, + value: senderBalance + ONE_WEI, + chainId, + }, + ]) ).rejects.toThrow(); });