@bananapus/nana-sdk-core 3.5.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wallet-check.mjs +495 -0
- package/dist/cjs/actions/suckerPairs.d.ts +5 -3
- package/dist/cjs/actions/suckerPairs.d.ts.map +1 -1
- package/dist/cjs/actions/suckerPairs.js +6 -26
- package/dist/cjs/actions/suckerPairs.js.map +1 -1
- package/dist/cjs/pause.d.ts +2 -0
- package/dist/cjs/pause.d.ts.map +1 -1
- package/dist/cjs/pause.js +17 -0
- package/dist/cjs/pause.js.map +1 -1
- package/dist/cjs/review/confirm.d.ts +56 -0
- package/dist/cjs/review/confirm.d.ts.map +1 -0
- package/dist/cjs/review/confirm.js +99 -0
- package/dist/cjs/review/confirm.js.map +1 -0
- package/dist/cjs/review/contractWrite.d.ts +5 -3
- package/dist/cjs/review/contractWrite.d.ts.map +1 -1
- package/dist/cjs/review/contractWrite.js +10 -8
- package/dist/cjs/review/contractWrite.js.map +1 -1
- package/dist/cjs/review/decode.d.ts +19 -1
- package/dist/cjs/review/decode.d.ts.map +1 -1
- package/dist/cjs/review/decode.js +30 -11
- package/dist/cjs/review/decode.js.map +1 -1
- package/dist/cjs/review/decodeCall.d.ts +21 -0
- package/dist/cjs/review/decodeCall.d.ts.map +1 -0
- package/dist/cjs/review/decodeCall.js +183 -0
- package/dist/cjs/review/decodeCall.js.map +1 -0
- package/dist/cjs/review/heldWrite.d.ts +4 -0
- package/dist/cjs/review/heldWrite.d.ts.map +1 -1
- package/dist/cjs/review/heldWrite.js +35 -31
- package/dist/cjs/review/heldWrite.js.map +1 -1
- package/dist/cjs/review/index.d.ts +12 -1
- package/dist/cjs/review/index.d.ts.map +1 -1
- package/dist/cjs/review/index.js +29 -1
- package/dist/cjs/review/index.js.map +1 -1
- package/dist/cjs/review/intents.d.ts +10 -0
- package/dist/cjs/review/intents.d.ts.map +1 -1
- package/dist/cjs/review/intents.js +13 -0
- package/dist/cjs/review/intents.js.map +1 -1
- package/dist/cjs/review/lineError.d.ts +72 -0
- package/dist/cjs/review/lineError.d.ts.map +1 -0
- package/dist/cjs/review/lineError.js +76 -0
- package/dist/cjs/review/lineError.js.map +1 -0
- package/dist/cjs/review/messages.d.ts +15 -3
- package/dist/cjs/review/messages.d.ts.map +1 -1
- package/dist/cjs/review/messages.js +16 -4
- package/dist/cjs/review/messages.js.map +1 -1
- package/dist/cjs/review/model.d.ts +149 -0
- package/dist/cjs/review/model.d.ts.map +1 -0
- package/dist/cjs/review/model.js +750 -0
- package/dist/cjs/review/model.js.map +1 -0
- package/dist/cjs/review/plan.d.ts +61 -0
- package/dist/cjs/review/plan.d.ts.map +1 -0
- package/dist/cjs/review/plan.js +143 -0
- package/dist/cjs/review/plan.js.map +1 -0
- package/dist/cjs/review/react/index.d.ts +1 -0
- package/dist/cjs/review/react/index.d.ts.map +1 -1
- package/dist/cjs/review/react/index.js +1 -0
- package/dist/cjs/review/react/index.js.map +1 -1
- package/dist/cjs/review/react/useHeldSend.js +3 -4
- package/dist/cjs/review/react/useHeldSend.js.map +1 -1
- package/dist/cjs/review/react/useHeldWrite.d.ts.map +1 -1
- package/dist/cjs/review/react/useHeldWrite.js +2 -1
- package/dist/cjs/review/react/useHeldWrite.js.map +1 -1
- package/dist/cjs/review/react/useReviewedRun.d.ts +122 -0
- package/dist/cjs/review/react/useReviewedRun.d.ts.map +1 -0
- package/dist/cjs/review/react/useReviewedRun.js +443 -0
- package/dist/cjs/review/react/useReviewedRun.js.map +1 -0
- package/dist/cjs/review/receipt.d.ts +37 -1
- package/dist/cjs/review/receipt.d.ts.map +1 -1
- package/dist/cjs/review/receipt.js +76 -3
- package/dist/cjs/review/receipt.js.map +1 -1
- package/dist/cjs/review/relayr.d.ts +13 -4
- package/dist/cjs/review/relayr.d.ts.map +1 -1
- package/dist/cjs/review/relayr.js +52 -8
- package/dist/cjs/review/relayr.js.map +1 -1
- package/dist/cjs/review/relayrSend.d.ts +106 -0
- package/dist/cjs/review/relayrSend.d.ts.map +1 -0
- package/dist/cjs/review/relayrSend.js +795 -0
- package/dist/cjs/review/relayrSend.js.map +1 -0
- package/dist/cjs/review/reviewQueue.d.ts +10 -9
- package/dist/cjs/review/reviewQueue.d.ts.map +1 -1
- package/dist/cjs/review/reviewQueue.js +19 -29
- package/dist/cjs/review/reviewQueue.js.map +1 -1
- package/dist/cjs/review/reviewRequests.d.ts +86 -0
- package/dist/cjs/review/reviewRequests.d.ts.map +1 -0
- package/dist/cjs/review/reviewRequests.js +266 -0
- package/dist/cjs/review/reviewRequests.js.map +1 -0
- package/dist/cjs/review/safeRelayr.d.ts +3 -1
- package/dist/cjs/review/safeRelayr.d.ts.map +1 -1
- package/dist/cjs/review/safeRelayr.js +9 -9
- package/dist/cjs/review/safeRelayr.js.map +1 -1
- package/dist/cjs/review/safeSend.d.ts +69 -0
- package/dist/cjs/review/safeSend.d.ts.map +1 -0
- package/dist/cjs/review/safeSend.js +306 -0
- package/dist/cjs/review/safeSend.js.map +1 -0
- package/dist/cjs/review/send.d.ts +272 -0
- package/dist/cjs/review/send.d.ts.map +1 -0
- package/dist/cjs/review/send.js +1163 -0
- package/dist/cjs/review/send.js.map +1 -0
- package/dist/cjs/review/sequence.d.ts +77 -0
- package/dist/cjs/review/sequence.d.ts.map +1 -0
- package/dist/cjs/review/sequence.js +232 -0
- package/dist/cjs/review/sequence.js.map +1 -0
- package/dist/cjs/review/sign.d.ts +111 -0
- package/dist/cjs/review/sign.d.ts.map +1 -0
- package/dist/cjs/review/sign.js +378 -0
- package/dist/cjs/review/sign.js.map +1 -0
- package/dist/cjs/review/simulation.d.ts +41 -2
- package/dist/cjs/review/simulation.d.ts.map +1 -1
- package/dist/cjs/review/simulation.js +99 -29
- package/dist/cjs/review/simulation.js.map +1 -1
- package/dist/cjs/review/transaction-message.d.ts +13 -0
- package/dist/cjs/review/transaction-message.d.ts.map +1 -1
- package/dist/cjs/review/transaction-message.js +25 -0
- package/dist/cjs/review/transaction-message.js.map +1 -1
- package/dist/cjs/review/transactionReview.d.ts +27 -19
- package/dist/cjs/review/transactionReview.d.ts.map +1 -1
- package/dist/cjs/review/transactionReview.js +22 -63
- package/dist/cjs/review/transactionReview.js.map +1 -1
- package/dist/cjs/review/wallet.d.ts +98 -0
- package/dist/cjs/review/wallet.d.ts.map +1 -0
- package/dist/cjs/review/wallet.js +234 -0
- package/dist/cjs/review/wallet.js.map +1 -0
- package/dist/cjs/review/writeHold.d.ts +138 -13
- package/dist/cjs/review/writeHold.d.ts.map +1 -1
- package/dist/cjs/review/writeHold.js +98 -18
- package/dist/cjs/review/writeHold.js.map +1 -1
- package/dist/cjs/review/writeRecovery.d.ts +17 -1
- package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
- package/dist/cjs/review/writeRecovery.js +146 -45
- package/dist/cjs/review/writeRecovery.js.map +1 -1
- package/dist/cjs/safeService.d.ts +27 -4
- package/dist/cjs/safeService.d.ts.map +1 -1
- package/dist/cjs/safeService.js +120 -3
- package/dist/cjs/safeService.js.map +1 -1
- package/dist/cjs/testing.d.ts +24 -0
- package/dist/cjs/testing.d.ts.map +1 -1
- package/dist/cjs/testing.js +26 -1
- package/dist/cjs/testing.js.map +1 -1
- package/dist/cjs/v6/defifa.d.ts.map +1 -1
- package/dist/cjs/v6/defifa.js +24 -23
- package/dist/cjs/v6/defifa.js.map +1 -1
- package/dist/cjs/v6/executionErrors.d.ts.map +1 -1
- package/dist/cjs/v6/executionErrors.js +3 -2
- package/dist/cjs/v6/executionErrors.js.map +1 -1
- package/dist/cjs/v6/pay.d.ts +25 -8
- package/dist/cjs/v6/pay.d.ts.map +1 -1
- package/dist/cjs/v6/pay.js +117 -23
- package/dist/cjs/v6/pay.js.map +1 -1
- package/dist/cjs/v6/suckers.d.ts +30 -0
- package/dist/cjs/v6/suckers.d.ts.map +1 -1
- package/dist/cjs/v6/suckers.js +69 -5
- package/dist/cjs/v6/suckers.js.map +1 -1
- package/dist/esm/actions/suckerPairs.d.ts +5 -3
- package/dist/esm/actions/suckerPairs.d.ts.map +1 -1
- package/dist/esm/actions/suckerPairs.js +7 -27
- package/dist/esm/actions/suckerPairs.js.map +1 -1
- package/dist/esm/pause.d.ts +2 -0
- package/dist/esm/pause.d.ts.map +1 -1
- package/dist/esm/pause.js +16 -0
- package/dist/esm/pause.js.map +1 -1
- package/dist/esm/review/confirm.d.ts +56 -0
- package/dist/esm/review/confirm.d.ts.map +1 -0
- package/dist/esm/review/confirm.js +93 -0
- package/dist/esm/review/confirm.js.map +1 -0
- package/dist/esm/review/contractWrite.d.ts +5 -3
- package/dist/esm/review/contractWrite.d.ts.map +1 -1
- package/dist/esm/review/contractWrite.js +10 -8
- package/dist/esm/review/contractWrite.js.map +1 -1
- package/dist/esm/review/decode.d.ts +19 -1
- package/dist/esm/review/decode.d.ts.map +1 -1
- package/dist/esm/review/decode.js +29 -14
- package/dist/esm/review/decode.js.map +1 -1
- package/dist/esm/review/decodeCall.d.ts +21 -0
- package/dist/esm/review/decodeCall.d.ts.map +1 -0
- package/dist/esm/review/decodeCall.js +180 -0
- package/dist/esm/review/decodeCall.js.map +1 -0
- package/dist/esm/review/heldWrite.d.ts +4 -0
- package/dist/esm/review/heldWrite.d.ts.map +1 -1
- package/dist/esm/review/heldWrite.js +34 -30
- package/dist/esm/review/heldWrite.js.map +1 -1
- package/dist/esm/review/index.d.ts +12 -1
- package/dist/esm/review/index.d.ts.map +1 -1
- package/dist/esm/review/index.js +12 -1
- package/dist/esm/review/index.js.map +1 -1
- package/dist/esm/review/intents.d.ts +10 -0
- package/dist/esm/review/intents.d.ts.map +1 -1
- package/dist/esm/review/intents.js +12 -0
- package/dist/esm/review/intents.js.map +1 -1
- package/dist/esm/review/lineError.d.ts +72 -0
- package/dist/esm/review/lineError.d.ts.map +1 -0
- package/dist/esm/review/lineError.js +68 -0
- package/dist/esm/review/lineError.js.map +1 -0
- package/dist/esm/review/messages.d.ts +15 -3
- package/dist/esm/review/messages.d.ts.map +1 -1
- package/dist/esm/review/messages.js +15 -3
- package/dist/esm/review/messages.js.map +1 -1
- package/dist/esm/review/model.d.ts +149 -0
- package/dist/esm/review/model.d.ts.map +1 -0
- package/dist/esm/review/model.js +746 -0
- package/dist/esm/review/model.js.map +1 -0
- package/dist/esm/review/plan.d.ts +61 -0
- package/dist/esm/review/plan.d.ts.map +1 -0
- package/dist/esm/review/plan.js +136 -0
- package/dist/esm/review/plan.js.map +1 -0
- package/dist/esm/review/react/index.d.ts +1 -0
- package/dist/esm/review/react/index.d.ts.map +1 -1
- package/dist/esm/review/react/index.js +1 -0
- package/dist/esm/review/react/index.js.map +1 -1
- package/dist/esm/review/react/useHeldSend.js +3 -4
- package/dist/esm/review/react/useHeldSend.js.map +1 -1
- package/dist/esm/review/react/useHeldWrite.d.ts.map +1 -1
- package/dist/esm/review/react/useHeldWrite.js +2 -1
- package/dist/esm/review/react/useHeldWrite.js.map +1 -1
- package/dist/esm/review/react/useReviewedRun.d.ts +122 -0
- package/dist/esm/review/react/useReviewedRun.d.ts.map +1 -0
- package/dist/esm/review/react/useReviewedRun.js +439 -0
- package/dist/esm/review/react/useReviewedRun.js.map +1 -0
- package/dist/esm/review/receipt.d.ts +37 -1
- package/dist/esm/review/receipt.d.ts.map +1 -1
- package/dist/esm/review/receipt.js +72 -2
- package/dist/esm/review/receipt.js.map +1 -1
- package/dist/esm/review/relayr.d.ts +13 -4
- package/dist/esm/review/relayr.d.ts.map +1 -1
- package/dist/esm/review/relayr.js +51 -8
- package/dist/esm/review/relayr.js.map +1 -1
- package/dist/esm/review/relayrSend.d.ts +106 -0
- package/dist/esm/review/relayrSend.d.ts.map +1 -0
- package/dist/esm/review/relayrSend.js +787 -0
- package/dist/esm/review/relayrSend.js.map +1 -0
- package/dist/esm/review/reviewQueue.d.ts +10 -9
- package/dist/esm/review/reviewQueue.d.ts.map +1 -1
- package/dist/esm/review/reviewQueue.js +17 -26
- package/dist/esm/review/reviewQueue.js.map +1 -1
- package/dist/esm/review/reviewRequests.d.ts +86 -0
- package/dist/esm/review/reviewRequests.d.ts.map +1 -0
- package/dist/esm/review/reviewRequests.js +263 -0
- package/dist/esm/review/reviewRequests.js.map +1 -0
- package/dist/esm/review/safeRelayr.d.ts +3 -1
- package/dist/esm/review/safeRelayr.d.ts.map +1 -1
- package/dist/esm/review/safeRelayr.js +9 -9
- package/dist/esm/review/safeRelayr.js.map +1 -1
- package/dist/esm/review/safeSend.d.ts +69 -0
- package/dist/esm/review/safeSend.d.ts.map +1 -0
- package/dist/esm/review/safeSend.js +301 -0
- package/dist/esm/review/safeSend.js.map +1 -0
- package/dist/esm/review/send.d.ts +272 -0
- package/dist/esm/review/send.d.ts.map +1 -0
- package/dist/esm/review/send.js +1156 -0
- package/dist/esm/review/send.js.map +1 -0
- package/dist/esm/review/sequence.d.ts +77 -0
- package/dist/esm/review/sequence.d.ts.map +1 -0
- package/dist/esm/review/sequence.js +223 -0
- package/dist/esm/review/sequence.js.map +1 -0
- package/dist/esm/review/sign.d.ts +111 -0
- package/dist/esm/review/sign.d.ts.map +1 -0
- package/dist/esm/review/sign.js +372 -0
- package/dist/esm/review/sign.js.map +1 -0
- package/dist/esm/review/simulation.d.ts +41 -2
- package/dist/esm/review/simulation.d.ts.map +1 -1
- package/dist/esm/review/simulation.js +95 -29
- package/dist/esm/review/simulation.js.map +1 -1
- package/dist/esm/review/transaction-message.d.ts +13 -0
- package/dist/esm/review/transaction-message.d.ts.map +1 -1
- package/dist/esm/review/transaction-message.js +23 -0
- package/dist/esm/review/transaction-message.js.map +1 -1
- package/dist/esm/review/transactionReview.d.ts +27 -19
- package/dist/esm/review/transactionReview.d.ts.map +1 -1
- package/dist/esm/review/transactionReview.js +19 -56
- package/dist/esm/review/transactionReview.js.map +1 -1
- package/dist/esm/review/wallet.d.ts +98 -0
- package/dist/esm/review/wallet.d.ts.map +1 -0
- package/dist/esm/review/wallet.js +227 -0
- package/dist/esm/review/wallet.js.map +1 -0
- package/dist/esm/review/writeHold.d.ts +138 -13
- package/dist/esm/review/writeHold.d.ts.map +1 -1
- package/dist/esm/review/writeHold.js +96 -19
- package/dist/esm/review/writeHold.js.map +1 -1
- package/dist/esm/review/writeRecovery.d.ts +17 -1
- package/dist/esm/review/writeRecovery.d.ts.map +1 -1
- package/dist/esm/review/writeRecovery.js +149 -48
- package/dist/esm/review/writeRecovery.js.map +1 -1
- package/dist/esm/safeService.d.ts +27 -4
- package/dist/esm/safeService.d.ts.map +1 -1
- package/dist/esm/safeService.js +119 -4
- package/dist/esm/safeService.js.map +1 -1
- package/dist/esm/testing.d.ts +24 -0
- package/dist/esm/testing.d.ts.map +1 -1
- package/dist/esm/testing.js +23 -1
- package/dist/esm/testing.js.map +1 -1
- package/dist/esm/v6/defifa.d.ts.map +1 -1
- package/dist/esm/v6/defifa.js +24 -23
- package/dist/esm/v6/defifa.js.map +1 -1
- package/dist/esm/v6/executionErrors.d.ts.map +1 -1
- package/dist/esm/v6/executionErrors.js +3 -2
- package/dist/esm/v6/executionErrors.js.map +1 -1
- package/dist/esm/v6/pay.d.ts +25 -8
- package/dist/esm/v6/pay.d.ts.map +1 -1
- package/dist/esm/v6/pay.js +115 -22
- package/dist/esm/v6/pay.js.map +1 -1
- package/dist/esm/v6/suckers.d.ts +30 -0
- package/dist/esm/v6/suckers.d.ts.map +1 -1
- package/dist/esm/v6/suckers.js +67 -5
- package/dist/esm/v6/suckers.js.map +1 -1
- package/package.json +11 -2
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RELAYR_FEE_MARKUP_BPS = exports.RELAYR_FEE_MARGIN_BPS = void 0;
|
|
4
|
+
exports.relayrFeeCap = relayrFeeCap;
|
|
5
|
+
exports.signReviewedForwardRequests = signReviewedForwardRequests;
|
|
6
|
+
exports.payRelayrReviewed = payRelayrReviewed;
|
|
7
|
+
exports.pollRelayrBundle = pollRelayrBundle;
|
|
8
|
+
exports.safeRelayrWalletDeps = safeRelayrWalletDeps;
|
|
9
|
+
const viem_1 = require("viem");
|
|
10
|
+
const accounts_1 = require("viem/accounts");
|
|
11
|
+
const juicebox_js_1 = require("../generated/juicebox.js");
|
|
12
|
+
const safe_js_1 = require("../safe.js");
|
|
13
|
+
const safeService_js_1 = require("../safeService.js");
|
|
14
|
+
const confirm_js_1 = require("./confirm.js");
|
|
15
|
+
const contractWrite_js_1 = require("./contractWrite.js");
|
|
16
|
+
const decodeCall_js_1 = require("./decodeCall.js");
|
|
17
|
+
const model_js_1 = require("./model.js");
|
|
18
|
+
const plan_js_1 = require("./plan.js");
|
|
19
|
+
const receipt_js_1 = require("./receipt.js");
|
|
20
|
+
const relayr_js_1 = require("./relayr.js");
|
|
21
|
+
const lineError_js_1 = require("./lineError.js");
|
|
22
|
+
const sign_js_1 = require("./sign.js");
|
|
23
|
+
const simulation_js_1 = require("./simulation.js");
|
|
24
|
+
const wallet_js_1 = require("./wallet.js");
|
|
25
|
+
const writeHold_js_1 = require("./writeHold.js");
|
|
26
|
+
const writeRecovery_js_1 = require("./writeRecovery.js");
|
|
27
|
+
const pause_js_1 = require("../pause.js");
|
|
28
|
+
const heldWrite_js_1 = require("./heldWrite.js");
|
|
29
|
+
/**
|
|
30
|
+
* The margin over an estimated Relayr fee that the review shows as its cap,
|
|
31
|
+
* in basis points (11,000 = the estimate plus 10%). Identical requests quoted
|
|
32
|
+
* minutes apart on every Relayr chain differed by at most 1.4%, so a signed
|
|
33
|
+
* quote above the cap, which asks again, should be rare.
|
|
34
|
+
*/
|
|
35
|
+
exports.RELAYR_FEE_MARGIN_BPS = 11000n;
|
|
36
|
+
/**
|
|
37
|
+
* Relayr's fee over a request's gas limit at the chain's gas price, in basis
|
|
38
|
+
* points, for bounding a fee when no stand-in quote can be had: the highest
|
|
39
|
+
* ratio measured on a Relayr chain (7.95, Ethereum at 100,000 gas), rounded
|
|
40
|
+
* up. Most of a fee is fixed per chain, so the ratio falls as gas rises.
|
|
41
|
+
*/
|
|
42
|
+
exports.RELAYR_FEE_MARKUP_BPS = 80000n;
|
|
43
|
+
/** The gas a forwarder's `execute` adds around the request's own. */
|
|
44
|
+
const FORWARDER_OVERHEAD_GAS = 100000n;
|
|
45
|
+
const QUOTE_TIMEOUT_MS = 45_000;
|
|
46
|
+
/** How long the review waits on a stand-in quote before it bounds the fee locally. */
|
|
47
|
+
const STAND_IN_TIMEOUT_MS = 10_000;
|
|
48
|
+
const POLL_INTERVAL_MS = 2_500;
|
|
49
|
+
const POLL_TIMEOUT_MS = 5 * 60_000;
|
|
50
|
+
const BALANCE_OF_ABI = [
|
|
51
|
+
{
|
|
52
|
+
type: "function",
|
|
53
|
+
name: "balanceOf",
|
|
54
|
+
stateMutability: "view",
|
|
55
|
+
inputs: [{ name: "owner", type: "address" }],
|
|
56
|
+
outputs: [{ type: "uint256" }],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
const RELAYR_PAYMENT_ABI = [
|
|
60
|
+
{
|
|
61
|
+
type: "function",
|
|
62
|
+
name: "prepayment",
|
|
63
|
+
stateMutability: "payable",
|
|
64
|
+
inputs: [
|
|
65
|
+
{ name: "payment_uuid", type: "bytes16" },
|
|
66
|
+
{ name: "deadline", type: "uint40" },
|
|
67
|
+
],
|
|
68
|
+
outputs: [],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const ACCOUNT_CHANGED = "The connected account changed. Review again.";
|
|
72
|
+
const ORDINARY_WALLET = "Pay for relayed transactions from an ordinary wallet. Nothing was sent.";
|
|
73
|
+
/** The quote's payment deadline passed before the wallet opened. */
|
|
74
|
+
class QuoteExpiredError extends Error {
|
|
75
|
+
constructor() {
|
|
76
|
+
super("This Relayr quote expired. Review the action again for a new quote.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function forwarderOn(chainId) {
|
|
80
|
+
const forwarder = (0, relayr_js_1.relayrForwarder)(chainId);
|
|
81
|
+
if (!forwarder) {
|
|
82
|
+
throw new Error(`There is no Juicebox forwarder on chain ${chainId}.`);
|
|
83
|
+
}
|
|
84
|
+
return forwarder;
|
|
85
|
+
}
|
|
86
|
+
/** The connected wallet, as long as it is an ordinary one on `account` (or, with `safe`, a Safe app). */
|
|
87
|
+
async function requireOrdinaryWallet(adapter, account, safe = false) {
|
|
88
|
+
await (0, wallet_js_1.walletReady)(adapter);
|
|
89
|
+
if (adapter.kind !== "wallet")
|
|
90
|
+
throw (0, wallet_js_1.unsupportedWalletRequest)();
|
|
91
|
+
const connected = adapter.account();
|
|
92
|
+
if (!connected)
|
|
93
|
+
throw new Error("Connect a wallet first.");
|
|
94
|
+
if (connected.toLowerCase() !== account.toLowerCase()) {
|
|
95
|
+
throw new Error(ACCOUNT_CHANGED);
|
|
96
|
+
}
|
|
97
|
+
// Signed requests and their proof need an ordinary wallet's own transaction.
|
|
98
|
+
if (adapter.isSafe() && !safe)
|
|
99
|
+
throw new Error(ORDINARY_WALLET);
|
|
100
|
+
}
|
|
101
|
+
/** Whether `target` on `chainId` trusts the chain's canonical forwarder, so the real sender is kept. */
|
|
102
|
+
async function trustsForwarder(client, chainId, target) {
|
|
103
|
+
const forwarder = (0, relayr_js_1.relayrForwarder)(chainId);
|
|
104
|
+
if (!forwarder)
|
|
105
|
+
return false;
|
|
106
|
+
try {
|
|
107
|
+
const data = await (0, simulation_js_1.simulateStateChangingTransaction)(client, {
|
|
108
|
+
from: forwarder,
|
|
109
|
+
to: target,
|
|
110
|
+
data: (0, viem_1.encodeFunctionData)({
|
|
111
|
+
abi: relayr_js_1.TRUSTED_FORWARDER_ABI,
|
|
112
|
+
functionName: "isTrustedForwarder",
|
|
113
|
+
args: [forwarder],
|
|
114
|
+
}),
|
|
115
|
+
gas: 100000n,
|
|
116
|
+
});
|
|
117
|
+
return (0, viem_1.decodeFunctionResult)({
|
|
118
|
+
abi: relayr_js_1.TRUSTED_FORWARDER_ABI,
|
|
119
|
+
functionName: "isTrustedForwarder",
|
|
120
|
+
data,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The forward request each call is signed as: from `account`, with the
|
|
129
|
+
* forwarder nonce read now and the default deadline. Refuses more than one
|
|
130
|
+
* call per chain, chains outside one Relayr network family, a gas limit that
|
|
131
|
+
* is not positive, and a destination that does not trust the forwarder.
|
|
132
|
+
*/
|
|
133
|
+
async function forwardRequests(calls, account, clientFor) {
|
|
134
|
+
const read = [...calls].map(({ chainId, target, data, value, gas }) => ({
|
|
135
|
+
chainId,
|
|
136
|
+
target,
|
|
137
|
+
data,
|
|
138
|
+
value: value ?? 0n,
|
|
139
|
+
gas,
|
|
140
|
+
}));
|
|
141
|
+
if (!read.length)
|
|
142
|
+
throw new Error("Choose at least one chain.");
|
|
143
|
+
const chainIds = read.map((call) => call.chainId);
|
|
144
|
+
if (!(0, relayr_js_1.relayrSupportsChains)(chainIds)) {
|
|
145
|
+
throw new Error("Relayr runs one call per supported destination, all mainnets or all testnets.");
|
|
146
|
+
}
|
|
147
|
+
if (read.some((call) => typeof call.gas !== "bigint" || call.gas <= 0n)) {
|
|
148
|
+
throw new Error("Each relayed call needs a positive gas limit.");
|
|
149
|
+
}
|
|
150
|
+
const deadline = (0, relayr_js_1.relayrForwarderDeadline)();
|
|
151
|
+
return Promise.all(read.map(async (call) => {
|
|
152
|
+
const client = clientFor(call.chainId);
|
|
153
|
+
if (!(await trustsForwarder(client, call.chainId, call.target))) {
|
|
154
|
+
throw new Error(`The target on chain ${call.chainId} does not trust the canonical forwarder. Send this action directly.`);
|
|
155
|
+
}
|
|
156
|
+
const nonce = await client.readContract({
|
|
157
|
+
address: forwarderOn(call.chainId),
|
|
158
|
+
abi: juicebox_js_1.erc2771ForwarderAbi,
|
|
159
|
+
functionName: "nonces",
|
|
160
|
+
args: [account],
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
chainId: call.chainId,
|
|
164
|
+
request: {
|
|
165
|
+
from: account,
|
|
166
|
+
to: call.target,
|
|
167
|
+
value: call.value,
|
|
168
|
+
gas: call.gas,
|
|
169
|
+
nonce,
|
|
170
|
+
deadline,
|
|
171
|
+
data: call.data,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
/** The bundle entry that runs `request` with `signature` through the chain's forwarder. */
|
|
177
|
+
function entryOf(chainId, request, signature) {
|
|
178
|
+
const { from, to, value, gas, deadline, data } = request;
|
|
179
|
+
return {
|
|
180
|
+
chain: chainId,
|
|
181
|
+
target: forwarderOn(chainId),
|
|
182
|
+
data: (0, viem_1.encodeFunctionData)({
|
|
183
|
+
abi: juicebox_js_1.erc2771ForwarderAbi,
|
|
184
|
+
functionName: "execute",
|
|
185
|
+
args: [{ from, to, value, gas, deadline, data, signature }],
|
|
186
|
+
}),
|
|
187
|
+
value: value.toString(),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Post `entries` to Relayr and bind its quote to them (see {@link bindRelayrQuote}). */
|
|
191
|
+
async function postBundle(entries) {
|
|
192
|
+
const request = (0, relayr_js_1.relayrBundleRequest)(entries);
|
|
193
|
+
const response = await globalThis.fetch(`${relayr_js_1.RELAYR_API}/v1/bundle/prepaid`, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: { "Content-Type": "application/json" },
|
|
196
|
+
body: JSON.stringify(request),
|
|
197
|
+
signal: AbortSignal.timeout(QUOTE_TIMEOUT_MS),
|
|
198
|
+
});
|
|
199
|
+
return (0, relayr_js_1.bindRelayrQuote)(response, request);
|
|
200
|
+
}
|
|
201
|
+
/** Refuses while a payment held under `intent` may still land, or, for `paidOnce`, once one succeeded. */
|
|
202
|
+
function requireIntentClear(deps, account, intent) {
|
|
203
|
+
for (const chainId of intent.funding) {
|
|
204
|
+
const state = (0, writeHold_js_1.openStepHold)(deps.hold, account, false, {
|
|
205
|
+
chainId,
|
|
206
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
207
|
+
data: "0x",
|
|
208
|
+
intent: intent.key,
|
|
209
|
+
}).peek();
|
|
210
|
+
if (state.status !== "clear")
|
|
211
|
+
throw new writeHold_js_1.WriteHeldError(state);
|
|
212
|
+
if (intent.paidOnce && state.settled?.result === "success") {
|
|
213
|
+
throw new Error("This Relayr payment already succeeded. Do not pay again.");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** The quote's authenticated payment on `chainId`. */
|
|
218
|
+
function paymentOn(quote, chainId) {
|
|
219
|
+
const destinationChainIds = quote.expectedTransactions.map((binding) => binding.chain);
|
|
220
|
+
const option = (0, relayr_js_1.relayrPaymentOptions)(quote, destinationChainIds).find((payment) => payment.chain === chainId);
|
|
221
|
+
if (!option &&
|
|
222
|
+
(0, relayr_js_1.relayrPaymentOptions)(quote, destinationChainIds, 0).some((payment) => payment.chain === chainId)) {
|
|
223
|
+
// Offered, but already too close to its deadline to pay.
|
|
224
|
+
throw new QuoteExpiredError();
|
|
225
|
+
}
|
|
226
|
+
if (!option) {
|
|
227
|
+
throw new Error(`Relayr returned no payment option on chain ${chainId}. Nothing was paid.`);
|
|
228
|
+
}
|
|
229
|
+
return (0, relayr_js_1.relayrPaymentDetails)(option, {
|
|
230
|
+
bundleUuid: quote.bundle_uuid,
|
|
231
|
+
destinationChainIds,
|
|
232
|
+
expectedTransactions: quote.expectedTransactions,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** The calls a quote's bundle runs, as a plan holds them; a raw value that can't be read matches none. */
|
|
236
|
+
function quotedCalls(quote) {
|
|
237
|
+
return quote.expectedTransactions.map(({ chain, entry }) => {
|
|
238
|
+
const request = (0, relayr_js_1.relayrForwardRequest)(entry);
|
|
239
|
+
if (request) {
|
|
240
|
+
const { from, to, data, value, gas } = request;
|
|
241
|
+
return { chainId: chain, from, to, data, value, gas };
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
chainId: chain,
|
|
245
|
+
from: "relayr",
|
|
246
|
+
to: entry.target,
|
|
247
|
+
data: entry.data,
|
|
248
|
+
value: /^\d+$/u.test(entry.value) ? BigInt(entry.value) : -1n,
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function withMargin(fee) {
|
|
253
|
+
return (fee * exports.RELAYR_FEE_MARGIN_BPS + 9999n) / 10000n;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Relayr's fee for `calls` signed by a key made for this quote alone: the
|
|
257
|
+
* same chains, gas limits and values, so Relayr prices the same work. The key
|
|
258
|
+
* lives only in memory for this call and leaves it only inside the requests'
|
|
259
|
+
* signatures.
|
|
260
|
+
*/
|
|
261
|
+
async function standInFee(calls, paymentChainId, deadline) {
|
|
262
|
+
const signer = (0, accounts_1.privateKeyToAccount)((0, accounts_1.generatePrivateKey)());
|
|
263
|
+
const entries = await Promise.all(calls.map(async ({ chainId, target, data, value, gas, raw }) => {
|
|
264
|
+
// Anyone may run a raw call: it is its own stand-in.
|
|
265
|
+
if (raw)
|
|
266
|
+
return { chain: chainId, target, data, value: value.toString() };
|
|
267
|
+
const request = {
|
|
268
|
+
from: signer.address,
|
|
269
|
+
to: target,
|
|
270
|
+
value,
|
|
271
|
+
gas,
|
|
272
|
+
deadline,
|
|
273
|
+
data,
|
|
274
|
+
};
|
|
275
|
+
const signature = await signer.signTypedData({
|
|
276
|
+
domain: {
|
|
277
|
+
name: "Juicebox",
|
|
278
|
+
version: "1",
|
|
279
|
+
chainId,
|
|
280
|
+
verifyingContract: forwarderOn(chainId),
|
|
281
|
+
},
|
|
282
|
+
types: relayr_js_1.FORWARD_REQUEST_TYPES,
|
|
283
|
+
primaryType: "ForwardRequest",
|
|
284
|
+
// A key made now has used no forwarder nonce.
|
|
285
|
+
message: { ...request, nonce: 0n },
|
|
286
|
+
});
|
|
287
|
+
return entryOf(chainId, request, signature);
|
|
288
|
+
}));
|
|
289
|
+
return paymentOn(await postBundle(entries), paymentChainId).amount;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* The fee cap the review shows: Relayr's quote for stand-ins of `calls`, plus
|
|
293
|
+
* {@link RELAYR_FEE_MARGIN_BPS}. A call that runs only for the real sender
|
|
294
|
+
* (an owner's action) can't be stood in, so a call with the same chain, gas
|
|
295
|
+
* and calldata length that anyone can run (JBProjects' `balanceOf`) is quoted
|
|
296
|
+
* instead, with the calls' values added. Without either quote in time, the
|
|
297
|
+
* fee is bounded locally: each call's gas estimate through the forwarder as
|
|
298
|
+
* `account`, at the chain's gas price, times {@link RELAYR_FEE_MARKUP_BPS}.
|
|
299
|
+
*/
|
|
300
|
+
async function relayrFeeCap(calls, account, paymentChainId, clientFor) {
|
|
301
|
+
const read = calls.map((call) => ({ ...call, value: call.value ?? 0n }));
|
|
302
|
+
const deadline = (0, relayr_js_1.relayrForwarderDeadline)();
|
|
303
|
+
const values = read.reduce((sum, call) => sum + call.value, 0n);
|
|
304
|
+
const quoted = await (0, pause_js_1.timeout)(standInFee(read, paymentChainId, deadline).catch(async () => {
|
|
305
|
+
const filler = (0, accounts_1.privateKeyToAccount)((0, accounts_1.generatePrivateKey)()).address;
|
|
306
|
+
return ((await standInFee(read.map((call) => {
|
|
307
|
+
const probe = (0, viem_1.encodeFunctionData)({
|
|
308
|
+
abi: BALANCE_OF_ABI,
|
|
309
|
+
functionName: "balanceOf",
|
|
310
|
+
args: [filler],
|
|
311
|
+
});
|
|
312
|
+
const bytes = Math.max((call.data.length - probe.length) / 2, 0);
|
|
313
|
+
return {
|
|
314
|
+
...call,
|
|
315
|
+
raw: false,
|
|
316
|
+
target: juicebox_js_1.jbV6ContractAddress.JBProjects[call.chainId],
|
|
317
|
+
data: `${probe}${"00".repeat(bytes)}`,
|
|
318
|
+
value: 0n,
|
|
319
|
+
};
|
|
320
|
+
}), paymentChainId, deadline)) + values);
|
|
321
|
+
}), STAND_IN_TIMEOUT_MS).catch(() => null);
|
|
322
|
+
if (quoted !== null)
|
|
323
|
+
return withMargin(quoted);
|
|
324
|
+
let bound = values;
|
|
325
|
+
for (const call of read) {
|
|
326
|
+
const client = clientFor(call.chainId);
|
|
327
|
+
const estimate = await client
|
|
328
|
+
.estimateGas(call.raw
|
|
329
|
+
? { to: call.target, data: call.data, value: call.value }
|
|
330
|
+
: {
|
|
331
|
+
account: forwarderOn(call.chainId),
|
|
332
|
+
to: call.target,
|
|
333
|
+
// ERC-2771: the forwarder appends the real sender.
|
|
334
|
+
data: (0, viem_1.concat)([call.data, account]),
|
|
335
|
+
value: call.value,
|
|
336
|
+
})
|
|
337
|
+
.catch(() => call.gas);
|
|
338
|
+
const gas = (estimate > call.gas ? estimate : call.gas) +
|
|
339
|
+
(call.raw ? 0n : FORWARDER_OVERHEAD_GAS);
|
|
340
|
+
bound +=
|
|
341
|
+
(gas * (await client.getGasPrice()) * exports.RELAYR_FEE_MARKUP_BPS) / 10000n;
|
|
342
|
+
}
|
|
343
|
+
return withMargin(bound);
|
|
344
|
+
}
|
|
345
|
+
/** The payment as the review shows it: one call to Relayr's pinned payment contract. */
|
|
346
|
+
function paymentReviewCall(details, account, safe = false) {
|
|
347
|
+
const { functionName, args } = (0, viem_1.decodeFunctionData)({
|
|
348
|
+
abi: RELAYR_PAYMENT_ABI,
|
|
349
|
+
data: details.calldata,
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
chainId: details.chainId,
|
|
353
|
+
from: account,
|
|
354
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
355
|
+
data: details.calldata,
|
|
356
|
+
value: details.amount,
|
|
357
|
+
// A Safe signs the sent gas as safeTxGas; 0 makes a failed payment revert.
|
|
358
|
+
...(safe ? { safeTxGas: 0n } : { gas: relayr_js_1.RELAYR_PAYMENT_GAS }),
|
|
359
|
+
abi: RELAYR_PAYMENT_ABI,
|
|
360
|
+
functionName,
|
|
361
|
+
args,
|
|
362
|
+
label: "Pay network fee",
|
|
363
|
+
contractName: "Relayr payment",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function reviewPayment(details, account, action, safe = false) {
|
|
367
|
+
const model = (0, model_js_1.buildReviewModel)({
|
|
368
|
+
calls: [paymentReviewCall(details, account, safe)],
|
|
369
|
+
title: action.title ?? "Review network fee",
|
|
370
|
+
...(action.context ? { context: action.context } : {}),
|
|
371
|
+
});
|
|
372
|
+
await (0, confirm_js_1.promptReview)(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Send one reviewed payment without asking again: Relayr's pinned contract
|
|
376
|
+
* and selector only, from `account`, after the runtime and simulation checks,
|
|
377
|
+
* under the payment's write hold (or `intent`'s). The payment must be proven
|
|
378
|
+
* from the chain before the hash it was mined under comes back.
|
|
379
|
+
*/
|
|
380
|
+
async function sendPayment(details, account, deps, hooks = {}) {
|
|
381
|
+
const { adapter } = deps;
|
|
382
|
+
const safe = hooks.safe ?? false;
|
|
383
|
+
await requireOrdinaryWallet(adapter, account, safe);
|
|
384
|
+
const client = deps.clientFor(details.chainId);
|
|
385
|
+
const call = {
|
|
386
|
+
chainId: details.chainId,
|
|
387
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
388
|
+
data: details.calldata,
|
|
389
|
+
value: details.amount,
|
|
390
|
+
...(hooks.intent ? { intent: hooks.intent.key } : {}),
|
|
391
|
+
};
|
|
392
|
+
const handle = (0, writeHold_js_1.openStepHold)(deps.hold, account, safe, call);
|
|
393
|
+
/** The attempt is saved (`onSending`), and the wallet not yet asked. */
|
|
394
|
+
let saved = false;
|
|
395
|
+
let invoked = false;
|
|
396
|
+
/** The reservation this payment's send took. */
|
|
397
|
+
let reserved;
|
|
398
|
+
/** A proposal of exactly this payment its Safe already queued, sent in its place. */
|
|
399
|
+
let queued = null;
|
|
400
|
+
const attempt = () => {
|
|
401
|
+
const held = handle.peek();
|
|
402
|
+
if (held.status !== "clear")
|
|
403
|
+
throw new writeHold_js_1.WriteHeldError(held);
|
|
404
|
+
return handle.send(client, (gate) => (0, contractWrite_js_1.submitReviewedContractWrite)({
|
|
405
|
+
request: call,
|
|
406
|
+
expectedAccount: account,
|
|
407
|
+
review: () => Promise.resolve(),
|
|
408
|
+
switchChain: async (chainId) => {
|
|
409
|
+
if (adapter.chainId() !== chainId)
|
|
410
|
+
await adapter.switchChain(chainId);
|
|
411
|
+
},
|
|
412
|
+
currentAccount: () => adapter.account(),
|
|
413
|
+
reverify: async () => {
|
|
414
|
+
await hooks.recheck?.();
|
|
415
|
+
// A lapsed payment would only revert in simulation.
|
|
416
|
+
if ((0, relayr_js_1.quoteExpired)(details.deadline))
|
|
417
|
+
throw new QuoteExpiredError();
|
|
418
|
+
await (0, relayr_js_1.requireRelayrPaymentRuntime)(client);
|
|
419
|
+
},
|
|
420
|
+
simulate: async () => {
|
|
421
|
+
const [, found] = await Promise.all([
|
|
422
|
+
(0, relayr_js_1.simulateRelayrPayment)(client, {
|
|
423
|
+
from: account,
|
|
424
|
+
payment: details,
|
|
425
|
+
}),
|
|
426
|
+
// A Safe never proposes again a payment it already queued exactly.
|
|
427
|
+
safe && (0, safeService_js_1.hasSafeService)(details.chainId)
|
|
428
|
+
? (0, safeService_js_1.findPendingSafeAppProposal)(client, details.chainId, account, {
|
|
429
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
430
|
+
data: details.calldata,
|
|
431
|
+
value: details.amount,
|
|
432
|
+
}, deps.hold.safeService, true)
|
|
433
|
+
: null,
|
|
434
|
+
]);
|
|
435
|
+
queued = found;
|
|
436
|
+
return details;
|
|
437
|
+
},
|
|
438
|
+
beforeWrite: async () => {
|
|
439
|
+
const { intent } = hooks;
|
|
440
|
+
// Only the check and the reservation are under the lock: never a
|
|
441
|
+
// chain switch, simulation or wallet that might not answer.
|
|
442
|
+
await (intent
|
|
443
|
+
? (0, writeRecovery_js_1.browserLocks)().request(`nana-sdk:relayr-payment:${account.toLowerCase()}:${intent.key}`, { mode: "exclusive" }, async () => {
|
|
444
|
+
requireIntentClear(deps, account, intent);
|
|
445
|
+
await gate.beforeSend();
|
|
446
|
+
})
|
|
447
|
+
: gate.beforeSend());
|
|
448
|
+
reserved = handle.peek().reservation;
|
|
449
|
+
try {
|
|
450
|
+
await hooks.onSending?.();
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
gate.onRejected();
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
saved = true;
|
|
457
|
+
},
|
|
458
|
+
beforeSend: () => {
|
|
459
|
+
try {
|
|
460
|
+
if (details.target.toLowerCase() !== relayr_js_1.RELAYR_PAYMENT_ADDRESS ||
|
|
461
|
+
details.calldata.slice(0, 10).toLowerCase() !==
|
|
462
|
+
relayr_js_1.RELAYR_PAYMENT_SELECTOR) {
|
|
463
|
+
throw new Error("The Relayr payment is not to its pinned contract.");
|
|
464
|
+
}
|
|
465
|
+
if ((0, relayr_js_1.quoteExpired)(details.deadline))
|
|
466
|
+
throw new QuoteExpiredError();
|
|
467
|
+
if (adapter.isSafe() !== safe)
|
|
468
|
+
throw new Error(safe
|
|
469
|
+
? "Wallet connection changed. Review the payment again."
|
|
470
|
+
: ORDINARY_WALLET);
|
|
471
|
+
if (adapter.chainId() !== details.chainId) {
|
|
472
|
+
throw new Error("Your wallet changed networks. Review the payment again.");
|
|
473
|
+
}
|
|
474
|
+
// Last, after every callback above could have run.
|
|
475
|
+
if (adapter.account()?.toLowerCase() !== account.toLowerCase()) {
|
|
476
|
+
throw new Error(ACCOUNT_CHANGED);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
throw new relayr_js_1.RelayrPaymentNotSentError(error);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
onBeforeWriteAborted: () => gate.onRejected(),
|
|
484
|
+
onWriteRejected: () => gate.onRejected(),
|
|
485
|
+
onWriteSubmitted: async (submitted) => {
|
|
486
|
+
gate.afterSubmit(submitted, queued?.tx);
|
|
487
|
+
await hooks.onSent?.(submitted);
|
|
488
|
+
},
|
|
489
|
+
// The pinned contract and gas, never the quote's word.
|
|
490
|
+
write: async ({ chainId, calldata, amount }) => {
|
|
491
|
+
// An adopted proposal is already with the Safe: never "not sent".
|
|
492
|
+
invoked = true;
|
|
493
|
+
if (queued)
|
|
494
|
+
return queued.proposalHash;
|
|
495
|
+
try {
|
|
496
|
+
return await adapter.sendTransaction({
|
|
497
|
+
chainId,
|
|
498
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
499
|
+
data: calldata,
|
|
500
|
+
value: amount,
|
|
501
|
+
gas: safe ? 0n : relayr_js_1.RELAYR_PAYMENT_GAS,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
// Only the wallet's own 4001 says it declined.
|
|
506
|
+
throw (0, relayr_js_1.relayrWalletPaymentError)(error);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
}));
|
|
510
|
+
};
|
|
511
|
+
let hash;
|
|
512
|
+
try {
|
|
513
|
+
if (hooks.intent)
|
|
514
|
+
requireIntentClear(deps, account, hooks.intent);
|
|
515
|
+
hash = await attempt();
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
// Saved as sending, refused by a final gate, never given to the wallet.
|
|
519
|
+
if (saved && !invoked && !(error instanceof relayr_js_1.RelayrPaymentNotSentError)) {
|
|
520
|
+
throw new relayr_js_1.RelayrPaymentNotSentError(error);
|
|
521
|
+
}
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
// A proposal: its hold follows it to the Safe's execution.
|
|
525
|
+
if (safe)
|
|
526
|
+
return hash;
|
|
527
|
+
// From here the payment was sent: it ends proven, reverted or
|
|
528
|
+
// contradicted as the chain shows it, or unreadable with its hash, never
|
|
529
|
+
// as a bare read failure.
|
|
530
|
+
try {
|
|
531
|
+
// The watcher's receipt only says when to ask the hold: the hold checks
|
|
532
|
+
// the receipt itself and settles, so a reverted payment can be reviewed
|
|
533
|
+
// and sent again without waiting on a view. Its settlement is the only
|
|
534
|
+
// word on a revert; a success is proven below either way.
|
|
535
|
+
const seen = await (0, receipt_js_1.waitForTrackedReceipt)(client, hash);
|
|
536
|
+
const state = await handle.state(client).catch(() => undefined);
|
|
537
|
+
const settled = state?.status === "clear" &&
|
|
538
|
+
reserved !== undefined &&
|
|
539
|
+
state.reservation === reserved &&
|
|
540
|
+
state.settled &&
|
|
541
|
+
"hash" in state.settled
|
|
542
|
+
? state.settled
|
|
543
|
+
: undefined;
|
|
544
|
+
// A wallet that sped the payment up mined it under another hash.
|
|
545
|
+
const mined = settled?.hash ?? seen.transactionHash;
|
|
546
|
+
if (mined.toLowerCase() !== hash.toLowerCase()) {
|
|
547
|
+
try {
|
|
548
|
+
await hooks.onSent?.(mined);
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
throw new contractWrite_js_1.SubmittedContractWriteError(mined, error);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (settled?.result === "failed")
|
|
555
|
+
throw new lineError_js_1.ReviewedCallRevertedError(0, mined, await (0, receipt_js_1.heldReceipt)(settled, client, seen));
|
|
556
|
+
if (!settled && seen.status === "reverted")
|
|
557
|
+
throw new relayr_js_1.RelayrProofUnavailableError(`Could not read Relayr payment ${mined} on chain ${details.chainId}. Do not pay again; check it later.`);
|
|
558
|
+
// A proof the node can't read yet is read again, as long as `follow` would
|
|
559
|
+
// follow a step, a read that stalls included; a contradicting proof is
|
|
560
|
+
// never retried. Past that, it is unreadable, its hash kept in the line.
|
|
561
|
+
const deadline = Date.now() + heldWrite_js_1.FOLLOW_MS;
|
|
562
|
+
let unread;
|
|
563
|
+
for (;;) {
|
|
564
|
+
const left = deadline - Date.now();
|
|
565
|
+
let timer;
|
|
566
|
+
try {
|
|
567
|
+
const proven = await Promise.race([
|
|
568
|
+
(0, relayr_js_1.verifyRelayrPayment)(client, {
|
|
569
|
+
hash: mined,
|
|
570
|
+
from: account,
|
|
571
|
+
payment: details,
|
|
572
|
+
}).then(() => true),
|
|
573
|
+
new Promise((resolve) => {
|
|
574
|
+
timer = setTimeout(() => resolve(false), Math.max(left, 0));
|
|
575
|
+
}),
|
|
576
|
+
]);
|
|
577
|
+
if (proven)
|
|
578
|
+
return mined;
|
|
579
|
+
throw Object.assign(new relayr_js_1.RelayrProofUnavailableError(`Could not read Relayr payment ${mined} on chain ${details.chainId}. Do not pay again; check it later.`), { cause: unread });
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
if (!(error instanceof relayr_js_1.RelayrProofUnavailableError) ||
|
|
583
|
+
deadline - Date.now() <= 0)
|
|
584
|
+
throw error;
|
|
585
|
+
unread = error;
|
|
586
|
+
await (0, pause_js_1.pause)(Math.min(heldWrite_js_1.RECEIPT_RETRY_MS, deadline - Date.now()), undefined, () => null);
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
clearTimeout(timer);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
if (error instanceof lineError_js_1.ReviewedCallRevertedError ||
|
|
595
|
+
error instanceof relayr_js_1.RelayrProofError ||
|
|
596
|
+
error instanceof relayr_js_1.RelayrProofUnavailableError ||
|
|
597
|
+
error instanceof contractWrite_js_1.SubmittedContractWriteError)
|
|
598
|
+
throw error;
|
|
599
|
+
throw Object.assign(new relayr_js_1.RelayrProofUnavailableError(`Could not read Relayr payment ${hash} on chain ${details.chainId}. Do not pay again; check it later.`), { cause: error });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Signs `calls` as ERC-2771 forward requests after one review of the
|
|
604
|
+
* requests, decoded, and returns the Relayr bundle entries that run them.
|
|
605
|
+
* Each destination must trust the chain's canonical forwarder. Nothing is
|
|
606
|
+
* posted or paid.
|
|
607
|
+
*/
|
|
608
|
+
async function signReviewedForwardRequests(calls, action, deps) {
|
|
609
|
+
const { account } = action;
|
|
610
|
+
await requireOrdinaryWallet(deps.adapter, account);
|
|
611
|
+
const requests = await forwardRequests(calls, account, deps.clientFor);
|
|
612
|
+
const prepared = requests.map(({ chainId, request }) => (0, sign_js_1.prepareSignature)({ kind: "forward-request", chainId, request }, account));
|
|
613
|
+
const model = (0, model_js_1.buildReviewModel)({
|
|
614
|
+
calls: [],
|
|
615
|
+
signatures: prepared.map(({ review }) => review),
|
|
616
|
+
title: action.title ?? "Review relayed transactions",
|
|
617
|
+
...(action.context ? { context: action.context } : {}),
|
|
618
|
+
});
|
|
619
|
+
await (0, confirm_js_1.promptReview)(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
|
|
620
|
+
const signatures = await (0, sign_js_1.signPrepared)(prepared, account, deps.adapter);
|
|
621
|
+
return requests.map(({ chainId, request }, index) => entryOf(chainId, request, signatures[index]));
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Pays a Relayr quote after one review of the payment: the option on
|
|
625
|
+
* `chainId`, authenticated against the quote, sent to Relayr's pinned
|
|
626
|
+
* contract and proven from the chain. For a payment resumed apart from its
|
|
627
|
+
* signatures. A Safe app pays by proposal: `safeTxHash` comes back, held
|
|
628
|
+
* until the Safe runs it. `onSending` runs after the hold reserves, right
|
|
629
|
+
* before the wallet opens (a throw sends nothing); `onSent` gets every hash
|
|
630
|
+
* the payment is known by. With `plan` and `relayrRound`, a quote of exactly
|
|
631
|
+
* that round's calls spends the round's reviewed fee bound once: at most
|
|
632
|
+
* the bound pays with no prompt, and a higher fee, a spent round or another
|
|
633
|
+
* chain is asked.
|
|
634
|
+
*/
|
|
635
|
+
async function payRelayrReviewed(quote, chainId, action, deps) {
|
|
636
|
+
const { account } = action;
|
|
637
|
+
await (0, wallet_js_1.walletReady)(deps.adapter);
|
|
638
|
+
const safe = deps.adapter.isSafe();
|
|
639
|
+
await requireOrdinaryWallet(deps.adapter, account, safe);
|
|
640
|
+
const details = paymentOn(quote, chainId);
|
|
641
|
+
const intent = {
|
|
642
|
+
key: `relayr:${details.bundleUuid}`,
|
|
643
|
+
funding: (0, relayr_js_1.relayrPaymentChains)(quote.expectedTransactions.map((binding) => binding.chain)),
|
|
644
|
+
paidOnce: true,
|
|
645
|
+
};
|
|
646
|
+
requireIntentClear(deps, account, intent);
|
|
647
|
+
if (action.plan && action.relayrRound !== undefined) {
|
|
648
|
+
const cap = (0, plan_js_1.spendPlannedRelayrRound)(action.plan, account, action.relayrRound, quotedCalls(quote), details.chainId);
|
|
649
|
+
if (cap === undefined || details.amount > cap)
|
|
650
|
+
await reviewPayment(details, account, {
|
|
651
|
+
title: cap ? "The network fee went up" : "Review network fee",
|
|
652
|
+
...(action.context ? { context: action.context } : {}),
|
|
653
|
+
}, safe);
|
|
654
|
+
}
|
|
655
|
+
else if (action.plan)
|
|
656
|
+
(0, plan_js_1.spendPlan)(action.plan, account, [
|
|
657
|
+
{
|
|
658
|
+
chainId: details.chainId,
|
|
659
|
+
from: account,
|
|
660
|
+
to: relayr_js_1.RELAYR_PAYMENT_ADDRESS,
|
|
661
|
+
data: details.calldata,
|
|
662
|
+
value: details.amount,
|
|
663
|
+
},
|
|
664
|
+
]);
|
|
665
|
+
else
|
|
666
|
+
await reviewPayment(details, account, action, safe);
|
|
667
|
+
const hash = await sendPayment(details, account, deps, {
|
|
668
|
+
intent,
|
|
669
|
+
// Relayr's own word too, whatever this browser kept of earlier payments.
|
|
670
|
+
recheck: () => (0, relayr_js_1.requireRelayrBundleUnpaid)(details.bundleUuid),
|
|
671
|
+
...(action.onSending ? { onSending: action.onSending } : {}),
|
|
672
|
+
...(action.onSent ? { onSent: action.onSent } : {}),
|
|
673
|
+
safe,
|
|
674
|
+
});
|
|
675
|
+
return safe ? { safeTxHash: hash } : { hash };
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Relayr's bundle once every one of its `expectedCount` transactions reports
|
|
679
|
+
* success. Only `failed`, in any case, is final; every other state, and a
|
|
680
|
+
* read that fails, is polled again every 2.5 seconds, for up to 5 minutes.
|
|
681
|
+
* Relayr's word is not proof: verify the destinations from the chain.
|
|
682
|
+
*/
|
|
683
|
+
async function pollRelayrBundle(uuid, expectedCount, options = {}) {
|
|
684
|
+
if (!Number.isSafeInteger(expectedCount) || expectedCount < 1) {
|
|
685
|
+
throw new Error("Relayr polling requires the exact destination count.");
|
|
686
|
+
}
|
|
687
|
+
const { signal } = options;
|
|
688
|
+
const until = Date.now() + POLL_TIMEOUT_MS;
|
|
689
|
+
for (;;) {
|
|
690
|
+
signal?.throwIfAborted();
|
|
691
|
+
const bundle = await (0, relayr_js_1.readRelayrBundle)(uuid, {
|
|
692
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
693
|
+
}).catch(() => null);
|
|
694
|
+
const records = Array.isArray(bundle?.transactions)
|
|
695
|
+
? bundle.transactions
|
|
696
|
+
: [];
|
|
697
|
+
const failed = records.filter((record) => (0, relayr_js_1.relayrStateIsFailed)(record?.status?.state)).length;
|
|
698
|
+
if (failed) {
|
|
699
|
+
throw new Error(`Relayr could not run this on ${failed} chain${failed === 1 ? "" : "s"}.`);
|
|
700
|
+
}
|
|
701
|
+
if (bundle &&
|
|
702
|
+
records.length === expectedCount &&
|
|
703
|
+
records.every((record) => (0, relayr_js_1.relayrStateIsSuccess)(record?.status?.state))) {
|
|
704
|
+
return bundle;
|
|
705
|
+
}
|
|
706
|
+
if (Date.now() >= until) {
|
|
707
|
+
throw new Error(`Relayr is still processing paid bundle ${uuid}. Do not submit this action again; check it later.`);
|
|
708
|
+
}
|
|
709
|
+
await new Promise((resolve, reject) => {
|
|
710
|
+
const timer = setTimeout(resolve, POLL_INTERVAL_MS);
|
|
711
|
+
signal?.addEventListener("abort", () => {
|
|
712
|
+
clearTimeout(timer);
|
|
713
|
+
reject(signal.reason);
|
|
714
|
+
}, { once: true });
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/** A Safe execution as the review shows it: the Safe's `execTransaction`, with the call or MultiSend inside. */
|
|
719
|
+
function safeExecutionCall(entry) {
|
|
720
|
+
const decoded = (0, viem_1.decodeFunctionData)({ abi: safeService_js_1.SAFE_EXEC_ABI, data: entry.data });
|
|
721
|
+
if (decoded.functionName !== "execTransaction") {
|
|
722
|
+
throw new Error("The saved call is not a Safe execution.");
|
|
723
|
+
}
|
|
724
|
+
const [to, value, data, operation] = decoded.args;
|
|
725
|
+
const inner = (0, safe_js_1.multiSendCallsOf)({ to, data, operation }) ?? [
|
|
726
|
+
{ to, value, data },
|
|
727
|
+
];
|
|
728
|
+
return {
|
|
729
|
+
chainId: entry.chain,
|
|
730
|
+
to: entry.target,
|
|
731
|
+
data: entry.data,
|
|
732
|
+
value: BigInt(entry.value),
|
|
733
|
+
abi: safeService_js_1.SAFE_EXEC_ABI,
|
|
734
|
+
functionName: "execTransaction",
|
|
735
|
+
args: decoded.args,
|
|
736
|
+
contractName: "Safe",
|
|
737
|
+
calls: inner.map((call) => (0, decodeCall_js_1.decodeReviewCall)({ chainId: entry.chain, from: entry.target, ...call })),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* The wallet side of {@link createSafeRelayrController}: `review` shows the
|
|
742
|
+
* frozen Safe executions in core's review, and `sendPayment` reviews and
|
|
743
|
+
* sends the selected payment as {@link payRelayrReviewed} does, with the
|
|
744
|
+
* controller's checks and journal callbacks around the wallet. A quote paid
|
|
745
|
+
* before is paid again only when {@link requireRelayrRetry} clears every
|
|
746
|
+
* earlier payment.
|
|
747
|
+
*/
|
|
748
|
+
function safeRelayrWalletDeps(deps) {
|
|
749
|
+
return {
|
|
750
|
+
review: async (executions, { resumed }) => {
|
|
751
|
+
const model = (0, model_js_1.buildReviewModel)({
|
|
752
|
+
calls: executions.map(({ entry }) => safeExecutionCall(entry)),
|
|
753
|
+
title: `Review ${executions.length} Safe execution${executions.length === 1 ? "" : "s"}`,
|
|
754
|
+
confirmLabel: resumed
|
|
755
|
+
? "Use saved quote"
|
|
756
|
+
: "Review & get payment options",
|
|
757
|
+
});
|
|
758
|
+
await (0, confirm_js_1.promptReview)(model, Promise.resolve({ status: "passed" }), Promise.resolve(model));
|
|
759
|
+
},
|
|
760
|
+
sendPayment: async ({ session, payment, beforeSend, onSending, onSent, }) => {
|
|
761
|
+
const { account } = session;
|
|
762
|
+
await requireOrdinaryWallet(deps.adapter, account);
|
|
763
|
+
if (session.payments.length >= relayr_js_1.MAX_RELAYR_SENT_PAYMENTS) {
|
|
764
|
+
throw new Error("This Relayr quote was paid too many times to pay again. Keep it pending; do not pay again.");
|
|
765
|
+
}
|
|
766
|
+
if (!session.quote)
|
|
767
|
+
throw new Error("This Safe action has no quote to pay.");
|
|
768
|
+
const details = (0, relayr_js_1.relayrPaymentDetails)(payment, {
|
|
769
|
+
bundleUuid: session.quote.bundle_uuid,
|
|
770
|
+
destinationChainIds: session.executions.map(({ entry }) => entry.chain),
|
|
771
|
+
});
|
|
772
|
+
await reviewPayment(details, account, {});
|
|
773
|
+
const payments = [...session.payments];
|
|
774
|
+
const hash = await sendPayment(details, account, deps, {
|
|
775
|
+
recheck: async () => {
|
|
776
|
+
await beforeSend();
|
|
777
|
+
if (session.payments.length) {
|
|
778
|
+
await (0, relayr_js_1.requireRelayrRetry)(deps.clientFor, {
|
|
779
|
+
payments: session.payments,
|
|
780
|
+
from: account,
|
|
781
|
+
bundleUuid: details.bundleUuid,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
onSending,
|
|
786
|
+
onSent: async (sent) => {
|
|
787
|
+
payments.push((0, relayr_js_1.sentRelayrPayment)(details, sent));
|
|
788
|
+
await onSent([...payments]);
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
return { hash, payments };
|
|
792
|
+
},
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
//# sourceMappingURL=relayrSend.js.map
|