Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 60 additions & 52 deletions bun.lock

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,25 @@
"node": ">=20.0.0"
},
"dependencies": {
"@reown/walletkit": "^1.2.4",
"@reown/walletkit": "^1.2.10",
"elliptic": "^6.6.1",
"js-sha3": "^0.9.3",
"near-api-js": "^6.2.4",
"viem": "^2.29.3"
"near-api-js": "^6.2.6",
"viem": "^2.36.0"
},
"devDependencies": {
"@types/elliptic": "^6.4.18",
"@types/jest": "^30.0.0",
"@types/node": "^24.2.0",
"@typescript-eslint/eslint-plugin": "^8.21.0",
"@typescript-eslint/parser": "^8.21.0",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^8.41.0",
"@typescript-eslint/parser": "^8.41.0",
"dotenv": "^17.2.1",
"eslint": "^9.19.0",
"ethers": "^6.13.3",
"opensea-js": "^7.1.12",
"prettier": "^3.5.3",
"ts-jest": "^29.2.3",
"tsx": "^4.16.2",
"typescript": "^5.7.3"
"eslint": "^9.34.0",
"ethers": "^6.15.0",
"opensea-js": "^7.2.1",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}
61 changes: 15 additions & 46 deletions src/mpcContract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contract, Account, transactions } from "near-api-js";
import { Account, transactions } from "near-api-js";
import { Address, Signature } from "viem";
import {
deriveChildPublicKey,
Expand All @@ -7,7 +7,7 @@ import {
uncompressedHexPointToEvmAddress,
} from "./utils";
import { TGAS } from "./chains";
import { MPCSignature, FunctionCallTransaction, SignArgs } from "./types";
import { FunctionCallTransaction, SignArgs } from "./types";
import { FinalExecutionOutcome } from "near-api-js/lib/providers";

/**
Expand All @@ -26,25 +26,14 @@ export interface ChangeMethodArgs<T> {
amount: string;
}

/** Interface extending the base NEAR Contract with MPC-specific methods */
interface MpcContractInterface extends Contract {
/** Returns the public key */
public_key: () => Promise<string>;
/** Returns required deposit based on current request queue */
experimental_signature_deposit: () => Promise<number>;
/** Signs a request using the MPC contract */
sign: (
args: ChangeMethodArgs<{ request: SignArgs }>
) => Promise<MPCSignature>;
}

/**
* High-level interface for the Near MPC-Recovery Contract
* located in: https://github.com/near/mpc-recovery
*/
export class MpcContract implements IMpcContract {
rootPublicKey: string | undefined;
contract: MpcContractInterface;
contractId: string;
// contract: MpcContractInterface;
connectedAccount: Account;

/**
Expand All @@ -57,12 +46,7 @@ export class MpcContract implements IMpcContract {
constructor(account: Account, contractId: string, rootPublicKey?: string) {
this.connectedAccount = account;
this.rootPublicKey = rootPublicKey;

this.contract = new Contract(account.getConnection(), contractId, {
changeMethods: ["sign"],
viewMethods: ["public_key", "experimental_signature_deposit"],
useLocalViewExecution: false,
}) as MpcContractInterface;
this.contractId = contractId;
}

/**
Expand All @@ -71,7 +55,7 @@ export class MpcContract implements IMpcContract {
* @returns The contract ID
*/
accountId(): string {
return this.contract.contractId;
return this.contractId;
}

/**
Expand All @@ -82,36 +66,22 @@ export class MpcContract implements IMpcContract {
*/
deriveEthAddress = async (derivationPath: string): Promise<Address> => {
if (!this.rootPublicKey) {
this.rootPublicKey = await this.contract.public_key();
this.rootPublicKey = await this.connectedAccount.provider.callFunction(
this.contractId,
"public_key",
{}
);
}

const publicKey = deriveChildPublicKey(
najPublicKeyStrToUncompressedHexPoint(this.rootPublicKey),
najPublicKeyStrToUncompressedHexPoint(this.rootPublicKey!),
this.connectedAccount.accountId,
derivationPath
);

return uncompressedHexPointToEvmAddress(publicKey);
};

/**
* Gets the required deposit for the signature
*
* @returns The required deposit amount as a string
*/
getDeposit = async (): Promise<string> => {
try {
const deposit = await this.contract.experimental_signature_deposit();
return BigInt(
deposit.toLocaleString("fullwide", { useGrouping: false })
).toString();
} catch {
// They are phasing out experimental_signature_deposit.
// required deposit is 1 yocto (see v1.signer-prod.testnet).
return "1";
}
};

/**
* Requests a signature from the MPC contract
*
Expand Down Expand Up @@ -141,15 +111,15 @@ export class MpcContract implements IMpcContract {
): Promise<FunctionCallTransaction<{ request: SignArgs }>> {
return {
signerId: this.connectedAccount.accountId,
receiverId: this.contract.contractId,
receiverId: this.contractId,
actions: [
{
type: "FunctionCall",
params: {
methodName: "sign",
args: { request: signArgs },
gas: gasOrDefault(gas),
deposit: await this.getDeposit(),
deposit: "1",
},
},
],
Expand All @@ -168,7 +138,7 @@ export class MpcContract implements IMpcContract {
): Promise<FinalExecutionOutcome> {
const account = this.connectedAccount;
const signedTx = await account.createSignedTransaction(
this.contract.contractId,
this.contractId,
transaction.actions.map(({ params: { args, gas, deposit } }) =>
transactions.functionCall("sign", args, BigInt(gas), BigInt(deposit))
)
Expand All @@ -195,7 +165,6 @@ export interface IMpcContract {
connectedAccount: Account;
accountId(): string;
deriveEthAddress(derivationPath: string): Promise<Address>;
getDeposit(): Promise<string>;
requestSignature(signArgs: SignArgs, gas?: bigint): Promise<Signature>;
encodeSignatureRequestTx(
signArgs: SignArgs,
Expand Down
11 changes: 1 addition & 10 deletions src/utils/mock-sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,6 @@ export class MockMpcContract implements IMpcContract {
return this.ethAccount.address;
};

/**
* Returns a mock deposit amount
*
* @returns A constant deposit value of "1"
*/
getDeposit = async (): Promise<string> => {
return "1";
};

/**
* Signs a message using the mock private key
*
Expand Down Expand Up @@ -110,7 +101,7 @@ export class MockMpcContract implements IMpcContract {
methodName: "sign",
args: { request: signArgs },
gas: gas ? gas.toString() : "1",
deposit: await this.getDeposit(),
deposit: "1",
},
},
],
Expand Down
8 changes: 2 additions & 6 deletions src/utils/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,9 @@ export async function relaySignedTransaction(
const tx = parseTransaction(serializedTransaction);
const network = Network.fromChainId(tx.chainId!);
if (wait) {
return network.client.sendRawTransaction({
serializedTransaction,
});
return network.client.sendRawTransaction({ serializedTransaction });
} else {
network.client.sendRawTransaction({
serializedTransaction,
});
network.client.sendRawTransaction({ serializedTransaction });
return keccak256(serializedTransaction);
}
}
Expand Down
10 changes: 6 additions & 4 deletions tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
recoverMessageAddress,
zeroAddress,
} from "viem";
dotenv.config();
dotenv.config({
override: true,
quiet: true,
});

describe("End To End", () => {
let mockedAdapter: NearEthAdapter;
Expand All @@ -32,13 +35,12 @@ 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,
})
).resolves.not.toThrow();
});
).resolves.toMatch(/^0x[a-fA-F0-9]{64}$/); // crude match for tx hash
}, 20000);

it.skip("signAndSendTransaction - Gnosis Chain", async () => {
await expect(
Expand Down