Skip to content

Commit

Permalink
Manually Sign, Broadcast and Poll Signature Requests (#153)
Browse files Browse the repository at this point in the history
The MPC Sign Requests are timing out when calling
`account.signAndSendTransaction`. This appears to be buried deeply in
the non-configurable `provider.txStatus` polling (when waiting for an
EXECUTED status).

Add alternate/semi-manual transaction broadcast & result fetching as
follows:

 - signTransaction
 - sendTransactionAsync
- poll txStatus for INCLUDED instead of EXECUTED on a loop until
EXECUTED (with 1 second/block sleep).


This is entirely due to some usability issue with near-js/providers:
near/near-api-js#1448
  • Loading branch information
bh2smith authored Jan 9, 2025
1 parent a23442a commit 007e55c
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 13 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"dependencies": {
"@walletconnect/web3wallet": "^1.13.0",
"elliptic": "^6.5.6",
"near-api-js": "^5.0.0",
"near-api-js": "^5.0.1",
"viem": "^2.21.37"
}
}
58 changes: 48 additions & 10 deletions src/mpcContract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contract, Account } from "near-api-js";
import { Contract, Account, transactions } from "near-api-js";
import { Address, Signature } from "viem";
import {
deriveChildPublicKey,
Expand All @@ -7,7 +7,8 @@ import {
} from "./utils/kdf";
import { TGAS } from "./chains/near";
import { MPCSignature, FunctionCallTransaction, SignArgs } from "./types";
import { transformSignature } from "./utils/signature";
import { signatureFromOutcome } from "./utils/signature";
import { FinalExecutionOutcome } from "near-api-js/lib/providers";

/**
* Near Contract Type for change methods.
Expand Down Expand Up @@ -107,14 +108,16 @@ export class MpcContract implements IMpcContract {
signArgs: SignArgs,
gas?: bigint
): Promise<Signature> => {
const mpcSig = await this.contract.sign({
signerAccount: this.connectedAccount,
args: { request: signArgs },
gas: gasOrDefault(gas),
amount: await this.getDeposit(),
});

return transformSignature(mpcSig);
// near-api-js SUX so bad we can't configure this RPC timeout.
// const mpcSig = await this.contract.sign({
// signerAccount: this.connectedAccount,
// args: { request: signArgs },
// gas: gasOrDefault(gas),
// amount: await this.getDeposit(),
// });
const transaction = await this.encodeSignatureRequestTx(signArgs, gas);
const outcome = await this.signAndSendSignRequest(transaction);
return signatureFromOutcome(outcome);
};

async encodeSignatureRequestTx(
Expand All @@ -137,6 +140,41 @@ export class MpcContract implements IMpcContract {
],
};
}

async signAndSendSignRequest(
transaction: FunctionCallTransaction<{ request: SignArgs }>,
blockTimeout: number = 30
): Promise<FinalExecutionOutcome> {
const account = this.connectedAccount;
// @ts-expect-error: Account.signTransaction is protected (for no apparantly good reason)
const [txHash, signedTx] = await account.signTransaction(
this.contract.contractId,
transaction.actions.map(({ params: { args, gas, deposit } }) =>
transactions.functionCall("sign", args, BigInt(gas), BigInt(deposit))
)
);
const provider = account.connection.provider;
let outcome = await provider.sendTransactionAsync(signedTx);

let pings = 0;
while (
outcome.final_execution_status != "EXECUTED" &&
pings < blockTimeout
) {
// Sleep 1 second before next ping.
await new Promise((resolve) => setTimeout(resolve, 1000));
// txStatus times out when waiting for 'EXECUTED'.
// Instead we wait for an earlier status type, sleep between and keep pinging.
outcome = await provider.txStatus(txHash, account.accountId, "INCLUDED");
pings += 1;
}
if (pings >= blockTimeout) {
console.warn(
`Request status polling exited before desired outcome.\n Current status: ${outcome.final_execution_status}\nSignature Request will likley fail.`
);
}
return outcome;
}
}

function gasOrDefault(gas?: bigint): string {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe("End To End", () => {
chainId,
})
).rejects.toThrow();
});
}, 15000);

it("signMessage", async () => {
const message = "NearEth";
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3794,7 +3794,7 @@ [email protected]:
dependencies:
"@types/json-schema" "^7.0.11"

near-api-js@^5.0.0:
near-api-js@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/near-api-js/-/near-api-js-5.0.1.tgz#a393547cabeeb7a8a445a4d786ec3aef948e0d84"
integrity sha512-ZSQYIQdekIvSRfOFuRj6MW11jtK5lsOaiWM2VB0nq7eROuuxwSSXHg+syjCXl3HNNZ3hzg0CNdp+5Za0X1ZtYg==
Expand Down

0 comments on commit 007e55c

Please sign in to comment.