@turnkey/gas-station 0.2.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/LICENSE +201 -0
- package/README.md +726 -0
- package/dist/abi/gas-station.d.ts +546 -0
- package/dist/abi/gas-station.d.ts.map +1 -0
- package/dist/abi/gas-station.js +275 -0
- package/dist/abi/gas-station.js.map +1 -0
- package/dist/abi/gas-station.mjs +273 -0
- package/dist/abi/gas-station.mjs.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +51 -0
- package/dist/config.mjs.map +1 -0
- package/dist/gasStationClient.d.ts +71 -0
- package/dist/gasStationClient.d.ts.map +1 -0
- package/dist/gasStationClient.js +237 -0
- package/dist/gasStationClient.js.map +1 -0
- package/dist/gasStationClient.mjs +235 -0
- package/dist/gasStationClient.mjs.map +1 -0
- package/dist/gasStationUtils.d.ts +7069 -0
- package/dist/gasStationUtils.d.ts.map +1 -0
- package/dist/gasStationUtils.js +129 -0
- package/dist/gasStationUtils.js.map +1 -0
- package/dist/gasStationUtils.mjs +120 -0
- package/dist/gasStationUtils.mjs.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/intentBuilder.d.ts +61 -0
- package/dist/intentBuilder.d.ts.map +1 -0
- package/dist/intentBuilder.js +153 -0
- package/dist/intentBuilder.js.map +1 -0
- package/dist/intentBuilder.mjs +151 -0
- package/dist/intentBuilder.mjs.map +1 -0
- package/dist/policyUtils.d.ts +271 -0
- package/dist/policyUtils.d.ts.map +1 -0
- package/dist/policyUtils.js +387 -0
- package/dist/policyUtils.js.map +1 -0
- package/dist/policyUtils.mjs +381 -0
- package/dist/policyUtils.mjs.map +1 -0
- package/package.json +47 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chains = require('viem/chains');
|
|
4
|
+
|
|
5
|
+
// Default contract addresses (deterministically deployed across all chains)
|
|
6
|
+
const DEFAULT_DELEGATE_CONTRACT = "0xC2a37Ee08cAc3778d9d05FF0a93FD5B553C77E3a";
|
|
7
|
+
const DEFAULT_EXECUTION_CONTRACT = "0x4ece92b06C7d2d99d87f052E0Fca47Fb180c3348";
|
|
8
|
+
// Chain preset configurations
|
|
9
|
+
const CHAIN_PRESETS = {
|
|
10
|
+
BASE_MAINNET: {
|
|
11
|
+
chain: chains.base,
|
|
12
|
+
rpcUrl: process.env.BASE_RPC_URL || "",
|
|
13
|
+
tokens: {
|
|
14
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
ETHEREUM_MAINNET: {
|
|
18
|
+
chain: chains.mainnet,
|
|
19
|
+
rpcUrl: process.env.ETH_RPC_URL || "",
|
|
20
|
+
tokens: {
|
|
21
|
+
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
SEPOLIA: {
|
|
25
|
+
chain: chains.sepolia,
|
|
26
|
+
rpcUrl: process.env.SEPOLIA_RPC_URL || "",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Create a custom preset with validation
|
|
31
|
+
*/
|
|
32
|
+
function createCustomPreset(config) {
|
|
33
|
+
if (!config.chain) {
|
|
34
|
+
throw new Error("Chain configuration is required");
|
|
35
|
+
}
|
|
36
|
+
if (!config.rpcUrl) {
|
|
37
|
+
throw new Error("RPC URL is required");
|
|
38
|
+
}
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a preset with optional environment variable overrides
|
|
43
|
+
*/
|
|
44
|
+
function getPreset(presetName, overrides) {
|
|
45
|
+
const preset = CHAIN_PRESETS[presetName];
|
|
46
|
+
if (!preset) {
|
|
47
|
+
throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CHAIN_PRESETS).join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
return { ...preset, ...overrides };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
exports.CHAIN_PRESETS = CHAIN_PRESETS;
|
|
53
|
+
exports.DEFAULT_DELEGATE_CONTRACT = DEFAULT_DELEGATE_CONTRACT;
|
|
54
|
+
exports.DEFAULT_EXECUTION_CONTRACT = DEFAULT_EXECUTION_CONTRACT;
|
|
55
|
+
exports.createCustomPreset = createCustomPreset;
|
|
56
|
+
exports.getPreset = getPreset;
|
|
57
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../src/config.ts"],"sourcesContent":[null],"names":["base","mainnet","sepolia"],"mappings":";;;;AAGA;AACO,MAAM,yBAAyB,GACpC;AACK,MAAM,0BAA0B,GACrC;AAsCF;AACO,MAAM,aAAa,GAAgC;AACxD,IAAA,YAAY,EAAE;AACZ,QAAA,KAAK,EAAEA,WAAI;AACX,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE;AACtC,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,4CAA4C;AACnD,SAAA;AACF,KAAA;AACD,IAAA,gBAAgB,EAAE;AAChB,QAAA,KAAK,EAAEC,cAAO;AACd,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE;AACrC,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,4CAA4C;AACnD,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAEC,cAAO;AACd,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE;AAC1C,KAAA;;AAGH;;AAEG;AACG,SAAU,kBAAkB,CAAC,MAAmB,EAAA;AACpD,IAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;AACjB,QAAA,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC;IACpD;AACA,IAAA,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;AAClB,QAAA,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC;IACxC;AACA,IAAA,OAAO,MAAM;AACf;AAEA;;AAEG;AACG,SAAU,SAAS,CACvB,UAAsC,EACtC,SAAgC,EAAA;AAEhC,IAAA,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE;AACX,QAAA,MAAM,IAAI,KAAK,CACb,mBAAmB,UAAU,CAAA,aAAA,EAAgB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE,CACrF;IACH;AACA,IAAA,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE;AACpC;;;;;;;;"}
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { sepolia, mainnet, base } from 'viem/chains';
|
|
2
|
+
|
|
3
|
+
// Default contract addresses (deterministically deployed across all chains)
|
|
4
|
+
const DEFAULT_DELEGATE_CONTRACT = "0xC2a37Ee08cAc3778d9d05FF0a93FD5B553C77E3a";
|
|
5
|
+
const DEFAULT_EXECUTION_CONTRACT = "0x4ece92b06C7d2d99d87f052E0Fca47Fb180c3348";
|
|
6
|
+
// Chain preset configurations
|
|
7
|
+
const CHAIN_PRESETS = {
|
|
8
|
+
BASE_MAINNET: {
|
|
9
|
+
chain: base,
|
|
10
|
+
rpcUrl: process.env.BASE_RPC_URL || "",
|
|
11
|
+
tokens: {
|
|
12
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
ETHEREUM_MAINNET: {
|
|
16
|
+
chain: mainnet,
|
|
17
|
+
rpcUrl: process.env.ETH_RPC_URL || "",
|
|
18
|
+
tokens: {
|
|
19
|
+
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
SEPOLIA: {
|
|
23
|
+
chain: sepolia,
|
|
24
|
+
rpcUrl: process.env.SEPOLIA_RPC_URL || "",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Create a custom preset with validation
|
|
29
|
+
*/
|
|
30
|
+
function createCustomPreset(config) {
|
|
31
|
+
if (!config.chain) {
|
|
32
|
+
throw new Error("Chain configuration is required");
|
|
33
|
+
}
|
|
34
|
+
if (!config.rpcUrl) {
|
|
35
|
+
throw new Error("RPC URL is required");
|
|
36
|
+
}
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get a preset with optional environment variable overrides
|
|
41
|
+
*/
|
|
42
|
+
function getPreset(presetName, overrides) {
|
|
43
|
+
const preset = CHAIN_PRESETS[presetName];
|
|
44
|
+
if (!preset) {
|
|
45
|
+
throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CHAIN_PRESETS).join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
return { ...preset, ...overrides };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { CHAIN_PRESETS, DEFAULT_DELEGATE_CONTRACT, DEFAULT_EXECUTION_CONTRACT, createCustomPreset, getPreset };
|
|
51
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.mjs","sources":["../src/config.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;AACO,MAAM,yBAAyB,GACpC;AACK,MAAM,0BAA0B,GACrC;AAsCF;AACO,MAAM,aAAa,GAAgC;AACxD,IAAA,YAAY,EAAE;AACZ,QAAA,KAAK,EAAE,IAAI;AACX,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE;AACtC,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,4CAA4C;AACnD,SAAA;AACF,KAAA;AACD,IAAA,gBAAgB,EAAE;AAChB,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE;AACrC,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,4CAA4C;AACnD,SAAA;AACF,KAAA;AACD,IAAA,OAAO,EAAE;AACP,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE;AAC1C,KAAA;;AAGH;;AAEG;AACG,SAAU,kBAAkB,CAAC,MAAmB,EAAA;AACpD,IAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;AACjB,QAAA,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC;IACpD;AACA,IAAA,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;AAClB,QAAA,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC;IACxC;AACA,IAAA,OAAO,MAAM;AACf;AAEA;;AAEG;AACG,SAAU,SAAS,CACvB,UAAsC,EACtC,SAAgC,EAAA;AAEhC,IAAA,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE;AACX,QAAA,MAAM,IAAI,KAAK,CACb,mBAAmB,UAAU,CAAA,aAAA,EAAgB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE,CACrF;IACH;AACA,IAAA,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE;AACpC;;;;"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type SignedAuthorization } from "viem";
|
|
2
|
+
import type { GasStationConfig, ExecutionIntent } from "./config";
|
|
3
|
+
import { IntentBuilder } from "./intentBuilder";
|
|
4
|
+
export declare class GasStationClient {
|
|
5
|
+
private walletClient;
|
|
6
|
+
private publicClient;
|
|
7
|
+
private delegateContract;
|
|
8
|
+
private executionContract;
|
|
9
|
+
constructor(config: GasStationConfig);
|
|
10
|
+
/**
|
|
11
|
+
* Sign an EIP-7702 authorization to delegate control to the gas station contract
|
|
12
|
+
* Call this with an end-user client to get a signed authorization
|
|
13
|
+
* The authorization can then be submitted by the paymaster using submitAuthorization()
|
|
14
|
+
*/
|
|
15
|
+
signAuthorization(): Promise<SignedAuthorization>;
|
|
16
|
+
/**
|
|
17
|
+
* Submit signed EIP-7702 authorization transaction(s)
|
|
18
|
+
* Supports authorizing multiple EOAs in a single transaction
|
|
19
|
+
* Call this with a paymaster client to broadcast the authorization transaction
|
|
20
|
+
* The paymaster pays for the gas
|
|
21
|
+
*/
|
|
22
|
+
submitAuthorizations(authorizations: SignedAuthorization[]): Promise<{
|
|
23
|
+
txHash: `0x${string}`;
|
|
24
|
+
blockNumber: bigint;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if an EOA is delegated to the gas station contract
|
|
28
|
+
* If no address is provided, uses the signer's address
|
|
29
|
+
*/
|
|
30
|
+
isAuthorized(eoaAddress?: `0x${string}`): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Get the current nonce for an EOA address from the gas station contract
|
|
33
|
+
* If no address is provided, uses the signer's address
|
|
34
|
+
*/
|
|
35
|
+
getNonce(eoaAddress?: `0x${string}`): Promise<bigint>;
|
|
36
|
+
/**
|
|
37
|
+
* Attempts to get the revert reason from a failed transaction.
|
|
38
|
+
* Replays the transaction via eth_call to extract detailed error information.
|
|
39
|
+
*
|
|
40
|
+
* This helps surface on-chain failure reasons such as:
|
|
41
|
+
* - Out of gas errors
|
|
42
|
+
* - Revert with reason strings
|
|
43
|
+
* - Custom error selectors
|
|
44
|
+
* - Insufficient balance errors
|
|
45
|
+
*/
|
|
46
|
+
private getRevertReason;
|
|
47
|
+
/**
|
|
48
|
+
* Create an intent builder for composing complex transactions
|
|
49
|
+
* Call this with an end-user client to create intents for signing
|
|
50
|
+
*/
|
|
51
|
+
createIntent(): IntentBuilder;
|
|
52
|
+
/**
|
|
53
|
+
* Sign an execution transaction for a signed intent (paymaster signs, doesn't send)
|
|
54
|
+
* This is useful for testing policies - the paymaster attempts to sign the execution
|
|
55
|
+
* but doesn't actually broadcast it to the network.
|
|
56
|
+
* Call this with a paymaster client to test if the paymaster can sign the execution.
|
|
57
|
+
*/
|
|
58
|
+
signExecution(intent: ExecutionIntent): Promise<`0x${string}`>;
|
|
59
|
+
/**
|
|
60
|
+
* Execute a signed intent through the gas station contract.
|
|
61
|
+
* Packs the execution data according to the delegate contract's expected format and
|
|
62
|
+
* submits it via the execution contract.
|
|
63
|
+
* Call this with a paymaster client to submit and pay for the transaction.
|
|
64
|
+
*/
|
|
65
|
+
execute(intent: ExecutionIntent): Promise<{
|
|
66
|
+
txHash: `0x${string}`;
|
|
67
|
+
blockNumber: bigint;
|
|
68
|
+
gasUsed: bigint;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=gasStationClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gasStationClient.d.ts","sourceRoot":"","sources":["../src/gasStationClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAMzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAKlE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAMhD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,iBAAiB,CAAgB;gBAE7B,MAAM,EAAE,gBAAgB;IAYpC;;;;OAIG;IACG,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAUvD;;;;;OAKG;IACG,oBAAoB,CACxB,cAAc,EAAE,mBAAmB,EAAE,GACpC,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IA8B1D;;;OAGG;IACG,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAahE;;;OAGG;IACG,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAa3D;;;;;;;;;OASG;YACW,eAAe;IAkD7B;;;OAGG;IACH,YAAY,IAAI,aAAa;IAa7B;;;;;OAKG;IACG,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC;IAkCpE;;;;;OAKG;IACG,OAAO,CACX,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CA8C5E"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var viem = require('viem');
|
|
4
|
+
var gasStation = require('./abi/gas-station.js');
|
|
5
|
+
var config = require('./config.js');
|
|
6
|
+
var intentBuilder = require('./intentBuilder.js');
|
|
7
|
+
var gasStationUtils = require('./gasStationUtils.js');
|
|
8
|
+
|
|
9
|
+
class GasStationClient {
|
|
10
|
+
constructor(config$1) {
|
|
11
|
+
this.walletClient = config$1.walletClient;
|
|
12
|
+
this.publicClient = gasStationUtils.createPublicClientForChain(config$1.walletClient.chain, config$1.walletClient.transport.url);
|
|
13
|
+
this.delegateContract =
|
|
14
|
+
config$1.delegateContract ?? config.DEFAULT_DELEGATE_CONTRACT;
|
|
15
|
+
this.executionContract =
|
|
16
|
+
config$1.executionContract ?? config.DEFAULT_EXECUTION_CONTRACT;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sign an EIP-7702 authorization to delegate control to the gas station contract
|
|
20
|
+
* Call this with an end-user client to get a signed authorization
|
|
21
|
+
* The authorization can then be submitted by the paymaster using submitAuthorization()
|
|
22
|
+
*/
|
|
23
|
+
async signAuthorization() {
|
|
24
|
+
const authorization = await this.walletClient.signAuthorization({
|
|
25
|
+
contractAddress: this.delegateContract,
|
|
26
|
+
account: this.walletClient.account,
|
|
27
|
+
chainId: 0, // 0 means valid on any EIP-7702 compatible chain
|
|
28
|
+
});
|
|
29
|
+
return authorization;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Submit signed EIP-7702 authorization transaction(s)
|
|
33
|
+
* Supports authorizing multiple EOAs in a single transaction
|
|
34
|
+
* Call this with a paymaster client to broadcast the authorization transaction
|
|
35
|
+
* The paymaster pays for the gas
|
|
36
|
+
*/
|
|
37
|
+
async submitAuthorizations(authorizations) {
|
|
38
|
+
if (authorizations.length === 0) {
|
|
39
|
+
throw new Error("Authorization list cannot be empty");
|
|
40
|
+
}
|
|
41
|
+
const authTxHash = await this.walletClient.sendTransaction({
|
|
42
|
+
from: "0x0000000000000000000000000000000000000000",
|
|
43
|
+
authorizationList: authorizations,
|
|
44
|
+
to: "0x0000000000000000000000000000000000000000",
|
|
45
|
+
type: "eip7702",
|
|
46
|
+
account: this.walletClient.account,
|
|
47
|
+
});
|
|
48
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
49
|
+
hash: authTxHash,
|
|
50
|
+
});
|
|
51
|
+
if (receipt.status !== "success") {
|
|
52
|
+
// Try to get the revert reason if available
|
|
53
|
+
const revertReason = await this.getRevertReason(authTxHash);
|
|
54
|
+
throw new Error(`Authorization failed: ${revertReason || "Transaction reverted"}. ` +
|
|
55
|
+
`Gas used: ${receipt.gasUsed}/${receipt.cumulativeGasUsed}. ` +
|
|
56
|
+
`Transaction hash: ${authTxHash}`);
|
|
57
|
+
}
|
|
58
|
+
return { txHash: authTxHash, blockNumber: receipt.blockNumber };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if an EOA is delegated to the gas station contract
|
|
62
|
+
* If no address is provided, uses the signer's address
|
|
63
|
+
*/
|
|
64
|
+
async isAuthorized(eoaAddress) {
|
|
65
|
+
const address = eoaAddress ?? this.walletClient.account.address;
|
|
66
|
+
const isAuthorized = await this.publicClient.readContract({
|
|
67
|
+
address: this.executionContract,
|
|
68
|
+
abi: gasStation.gasStationAbi,
|
|
69
|
+
functionName: "isDelegated",
|
|
70
|
+
args: [address],
|
|
71
|
+
});
|
|
72
|
+
return isAuthorized;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the current nonce for an EOA address from the gas station contract
|
|
76
|
+
* If no address is provided, uses the signer's address
|
|
77
|
+
*/
|
|
78
|
+
async getNonce(eoaAddress) {
|
|
79
|
+
const address = eoaAddress ?? this.walletClient.account.address;
|
|
80
|
+
const nonce = await this.publicClient.readContract({
|
|
81
|
+
address: this.executionContract,
|
|
82
|
+
abi: gasStation.gasStationAbi,
|
|
83
|
+
functionName: "getNonce",
|
|
84
|
+
args: [address],
|
|
85
|
+
});
|
|
86
|
+
return nonce;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Attempts to get the revert reason from a failed transaction.
|
|
90
|
+
* Replays the transaction via eth_call to extract detailed error information.
|
|
91
|
+
*
|
|
92
|
+
* This helps surface on-chain failure reasons such as:
|
|
93
|
+
* - Out of gas errors
|
|
94
|
+
* - Revert with reason strings
|
|
95
|
+
* - Custom error selectors
|
|
96
|
+
* - Insufficient balance errors
|
|
97
|
+
*/
|
|
98
|
+
async getRevertReason(txHash) {
|
|
99
|
+
try {
|
|
100
|
+
const tx = await this.publicClient.getTransaction({ hash: txHash });
|
|
101
|
+
// Replay the transaction via eth_call to trigger the revert and get the reason
|
|
102
|
+
await this.publicClient.call({
|
|
103
|
+
to: tx.to,
|
|
104
|
+
data: tx.input,
|
|
105
|
+
gas: tx.gas,
|
|
106
|
+
value: tx.value,
|
|
107
|
+
});
|
|
108
|
+
// If call succeeds, we don't have a revert reason (shouldn't happen for failed tx)
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// Extract revert reason from error message
|
|
113
|
+
if (error?.details) {
|
|
114
|
+
return error.details;
|
|
115
|
+
}
|
|
116
|
+
if (error?.message) {
|
|
117
|
+
// Check for out of gas errors
|
|
118
|
+
const outOfGasMatch = error.message.match(/out of gas/i);
|
|
119
|
+
if (outOfGasMatch) {
|
|
120
|
+
return "Out of gas";
|
|
121
|
+
}
|
|
122
|
+
// Check for standard revert with reason string
|
|
123
|
+
const revertMatch = error.message.match(/reverted with reason string '([^']+)'/);
|
|
124
|
+
if (revertMatch) {
|
|
125
|
+
return revertMatch[1];
|
|
126
|
+
}
|
|
127
|
+
// Check for custom errors (Solidity 0.8.4+)
|
|
128
|
+
const customErrorMatch = error.message.match(/reverted with the following \(custom\) error:\s*([^\n]+)/);
|
|
129
|
+
if (customErrorMatch) {
|
|
130
|
+
return customErrorMatch[1];
|
|
131
|
+
}
|
|
132
|
+
// Return the full error message if no specific pattern matches
|
|
133
|
+
return error.message;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create an intent builder for composing complex transactions
|
|
140
|
+
* Call this with an end-user client to create intents for signing
|
|
141
|
+
*/
|
|
142
|
+
createIntent() {
|
|
143
|
+
if (!this.walletClient.account?.address) {
|
|
144
|
+
throw new Error(`Wallet client account is not properly configured. Account: ${JSON.stringify(this.walletClient.account)}`);
|
|
145
|
+
}
|
|
146
|
+
return intentBuilder.IntentBuilder.create({
|
|
147
|
+
eoaWalletClient: this.walletClient,
|
|
148
|
+
chainId: this.walletClient.chain.id,
|
|
149
|
+
eoaAddress: this.walletClient.account.address,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Sign an execution transaction for a signed intent (paymaster signs, doesn't send)
|
|
154
|
+
* This is useful for testing policies - the paymaster attempts to sign the execution
|
|
155
|
+
* but doesn't actually broadcast it to the network.
|
|
156
|
+
* Call this with a paymaster client to test if the paymaster can sign the execution.
|
|
157
|
+
*/
|
|
158
|
+
async signExecution(intent) {
|
|
159
|
+
// Pack the execution data (signature, nonce, deadline, args)
|
|
160
|
+
const packedData = gasStationUtils.packExecutionData({
|
|
161
|
+
signature: intent.signature,
|
|
162
|
+
nonce: intent.nonce,
|
|
163
|
+
deadline: intent.deadline,
|
|
164
|
+
args: intent.callData,
|
|
165
|
+
});
|
|
166
|
+
// Encode the function call data
|
|
167
|
+
const callData = viem.encodeFunctionData({
|
|
168
|
+
abi: gasStation.gasStationAbi,
|
|
169
|
+
functionName: "execute",
|
|
170
|
+
args: [
|
|
171
|
+
intent.eoaAddress,
|
|
172
|
+
intent.outputContract,
|
|
173
|
+
intent.ethAmount,
|
|
174
|
+
packedData,
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
// Sign the transaction without sending it
|
|
178
|
+
const signedTx = await this.walletClient.signTransaction({
|
|
179
|
+
to: this.executionContract,
|
|
180
|
+
data: callData,
|
|
181
|
+
gas: BigInt(200000),
|
|
182
|
+
type: "eip1559",
|
|
183
|
+
account: this.walletClient.account,
|
|
184
|
+
chain: this.walletClient.chain,
|
|
185
|
+
});
|
|
186
|
+
return signedTx;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Execute a signed intent through the gas station contract.
|
|
190
|
+
* Packs the execution data according to the delegate contract's expected format and
|
|
191
|
+
* submits it via the execution contract.
|
|
192
|
+
* Call this with a paymaster client to submit and pay for the transaction.
|
|
193
|
+
*/
|
|
194
|
+
async execute(intent) {
|
|
195
|
+
// Pack the execution data (signature, nonce, deadline, args)
|
|
196
|
+
const packedData = gasStationUtils.packExecutionData({
|
|
197
|
+
signature: intent.signature,
|
|
198
|
+
nonce: intent.nonce,
|
|
199
|
+
deadline: intent.deadline,
|
|
200
|
+
args: intent.callData,
|
|
201
|
+
});
|
|
202
|
+
// Call the unified execute function with explicit parameters
|
|
203
|
+
const txHash = await this.walletClient.sendTransaction({
|
|
204
|
+
to: this.executionContract,
|
|
205
|
+
data: viem.encodeFunctionData({
|
|
206
|
+
abi: gasStation.gasStationAbi,
|
|
207
|
+
functionName: "execute",
|
|
208
|
+
args: [
|
|
209
|
+
intent.eoaAddress,
|
|
210
|
+
intent.outputContract,
|
|
211
|
+
intent.ethAmount,
|
|
212
|
+
packedData,
|
|
213
|
+
],
|
|
214
|
+
}),
|
|
215
|
+
gas: BigInt(200000),
|
|
216
|
+
account: this.walletClient.account,
|
|
217
|
+
});
|
|
218
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
219
|
+
hash: txHash,
|
|
220
|
+
});
|
|
221
|
+
if (receipt.status !== "success") {
|
|
222
|
+
// Try to get the revert reason if available
|
|
223
|
+
const revertReason = await this.getRevertReason(txHash);
|
|
224
|
+
throw new Error(`Execution failed: ${revertReason || "Transaction reverted"}. ` +
|
|
225
|
+
`Gas used: ${receipt.gasUsed}/${receipt.cumulativeGasUsed}. ` +
|
|
226
|
+
`Transaction hash: ${txHash}`);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
txHash,
|
|
230
|
+
blockNumber: receipt.blockNumber,
|
|
231
|
+
gasUsed: receipt.gasUsed,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
exports.GasStationClient = GasStationClient;
|
|
237
|
+
//# sourceMappingURL=gasStationClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gasStationClient.js","sources":["../src/gasStationClient.ts"],"sourcesContent":[null],"names":["config","createPublicClientForChain","DEFAULT_DELEGATE_CONTRACT","DEFAULT_EXECUTION_CONTRACT","gasStationAbi","IntentBuilder","packExecutionData","encodeFunctionData"],"mappings":"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"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { encodeFunctionData } from 'viem';
|
|
2
|
+
import { gasStationAbi } from './abi/gas-station.mjs';
|
|
3
|
+
import { DEFAULT_DELEGATE_CONTRACT, DEFAULT_EXECUTION_CONTRACT } from './config.mjs';
|
|
4
|
+
import { IntentBuilder } from './intentBuilder.mjs';
|
|
5
|
+
import { createPublicClientForChain, packExecutionData } from './gasStationUtils.mjs';
|
|
6
|
+
|
|
7
|
+
class GasStationClient {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.walletClient = config.walletClient;
|
|
10
|
+
this.publicClient = createPublicClientForChain(config.walletClient.chain, config.walletClient.transport.url);
|
|
11
|
+
this.delegateContract =
|
|
12
|
+
config.delegateContract ?? DEFAULT_DELEGATE_CONTRACT;
|
|
13
|
+
this.executionContract =
|
|
14
|
+
config.executionContract ?? DEFAULT_EXECUTION_CONTRACT;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sign an EIP-7702 authorization to delegate control to the gas station contract
|
|
18
|
+
* Call this with an end-user client to get a signed authorization
|
|
19
|
+
* The authorization can then be submitted by the paymaster using submitAuthorization()
|
|
20
|
+
*/
|
|
21
|
+
async signAuthorization() {
|
|
22
|
+
const authorization = await this.walletClient.signAuthorization({
|
|
23
|
+
contractAddress: this.delegateContract,
|
|
24
|
+
account: this.walletClient.account,
|
|
25
|
+
chainId: 0, // 0 means valid on any EIP-7702 compatible chain
|
|
26
|
+
});
|
|
27
|
+
return authorization;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Submit signed EIP-7702 authorization transaction(s)
|
|
31
|
+
* Supports authorizing multiple EOAs in a single transaction
|
|
32
|
+
* Call this with a paymaster client to broadcast the authorization transaction
|
|
33
|
+
* The paymaster pays for the gas
|
|
34
|
+
*/
|
|
35
|
+
async submitAuthorizations(authorizations) {
|
|
36
|
+
if (authorizations.length === 0) {
|
|
37
|
+
throw new Error("Authorization list cannot be empty");
|
|
38
|
+
}
|
|
39
|
+
const authTxHash = await this.walletClient.sendTransaction({
|
|
40
|
+
from: "0x0000000000000000000000000000000000000000",
|
|
41
|
+
authorizationList: authorizations,
|
|
42
|
+
to: "0x0000000000000000000000000000000000000000",
|
|
43
|
+
type: "eip7702",
|
|
44
|
+
account: this.walletClient.account,
|
|
45
|
+
});
|
|
46
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
47
|
+
hash: authTxHash,
|
|
48
|
+
});
|
|
49
|
+
if (receipt.status !== "success") {
|
|
50
|
+
// Try to get the revert reason if available
|
|
51
|
+
const revertReason = await this.getRevertReason(authTxHash);
|
|
52
|
+
throw new Error(`Authorization failed: ${revertReason || "Transaction reverted"}. ` +
|
|
53
|
+
`Gas used: ${receipt.gasUsed}/${receipt.cumulativeGasUsed}. ` +
|
|
54
|
+
`Transaction hash: ${authTxHash}`);
|
|
55
|
+
}
|
|
56
|
+
return { txHash: authTxHash, blockNumber: receipt.blockNumber };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if an EOA is delegated to the gas station contract
|
|
60
|
+
* If no address is provided, uses the signer's address
|
|
61
|
+
*/
|
|
62
|
+
async isAuthorized(eoaAddress) {
|
|
63
|
+
const address = eoaAddress ?? this.walletClient.account.address;
|
|
64
|
+
const isAuthorized = await this.publicClient.readContract({
|
|
65
|
+
address: this.executionContract,
|
|
66
|
+
abi: gasStationAbi,
|
|
67
|
+
functionName: "isDelegated",
|
|
68
|
+
args: [address],
|
|
69
|
+
});
|
|
70
|
+
return isAuthorized;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the current nonce for an EOA address from the gas station contract
|
|
74
|
+
* If no address is provided, uses the signer's address
|
|
75
|
+
*/
|
|
76
|
+
async getNonce(eoaAddress) {
|
|
77
|
+
const address = eoaAddress ?? this.walletClient.account.address;
|
|
78
|
+
const nonce = await this.publicClient.readContract({
|
|
79
|
+
address: this.executionContract,
|
|
80
|
+
abi: gasStationAbi,
|
|
81
|
+
functionName: "getNonce",
|
|
82
|
+
args: [address],
|
|
83
|
+
});
|
|
84
|
+
return nonce;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Attempts to get the revert reason from a failed transaction.
|
|
88
|
+
* Replays the transaction via eth_call to extract detailed error information.
|
|
89
|
+
*
|
|
90
|
+
* This helps surface on-chain failure reasons such as:
|
|
91
|
+
* - Out of gas errors
|
|
92
|
+
* - Revert with reason strings
|
|
93
|
+
* - Custom error selectors
|
|
94
|
+
* - Insufficient balance errors
|
|
95
|
+
*/
|
|
96
|
+
async getRevertReason(txHash) {
|
|
97
|
+
try {
|
|
98
|
+
const tx = await this.publicClient.getTransaction({ hash: txHash });
|
|
99
|
+
// Replay the transaction via eth_call to trigger the revert and get the reason
|
|
100
|
+
await this.publicClient.call({
|
|
101
|
+
to: tx.to,
|
|
102
|
+
data: tx.input,
|
|
103
|
+
gas: tx.gas,
|
|
104
|
+
value: tx.value,
|
|
105
|
+
});
|
|
106
|
+
// If call succeeds, we don't have a revert reason (shouldn't happen for failed tx)
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// Extract revert reason from error message
|
|
111
|
+
if (error?.details) {
|
|
112
|
+
return error.details;
|
|
113
|
+
}
|
|
114
|
+
if (error?.message) {
|
|
115
|
+
// Check for out of gas errors
|
|
116
|
+
const outOfGasMatch = error.message.match(/out of gas/i);
|
|
117
|
+
if (outOfGasMatch) {
|
|
118
|
+
return "Out of gas";
|
|
119
|
+
}
|
|
120
|
+
// Check for standard revert with reason string
|
|
121
|
+
const revertMatch = error.message.match(/reverted with reason string '([^']+)'/);
|
|
122
|
+
if (revertMatch) {
|
|
123
|
+
return revertMatch[1];
|
|
124
|
+
}
|
|
125
|
+
// Check for custom errors (Solidity 0.8.4+)
|
|
126
|
+
const customErrorMatch = error.message.match(/reverted with the following \(custom\) error:\s*([^\n]+)/);
|
|
127
|
+
if (customErrorMatch) {
|
|
128
|
+
return customErrorMatch[1];
|
|
129
|
+
}
|
|
130
|
+
// Return the full error message if no specific pattern matches
|
|
131
|
+
return error.message;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Create an intent builder for composing complex transactions
|
|
138
|
+
* Call this with an end-user client to create intents for signing
|
|
139
|
+
*/
|
|
140
|
+
createIntent() {
|
|
141
|
+
if (!this.walletClient.account?.address) {
|
|
142
|
+
throw new Error(`Wallet client account is not properly configured. Account: ${JSON.stringify(this.walletClient.account)}`);
|
|
143
|
+
}
|
|
144
|
+
return IntentBuilder.create({
|
|
145
|
+
eoaWalletClient: this.walletClient,
|
|
146
|
+
chainId: this.walletClient.chain.id,
|
|
147
|
+
eoaAddress: this.walletClient.account.address,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Sign an execution transaction for a signed intent (paymaster signs, doesn't send)
|
|
152
|
+
* This is useful for testing policies - the paymaster attempts to sign the execution
|
|
153
|
+
* but doesn't actually broadcast it to the network.
|
|
154
|
+
* Call this with a paymaster client to test if the paymaster can sign the execution.
|
|
155
|
+
*/
|
|
156
|
+
async signExecution(intent) {
|
|
157
|
+
// Pack the execution data (signature, nonce, deadline, args)
|
|
158
|
+
const packedData = packExecutionData({
|
|
159
|
+
signature: intent.signature,
|
|
160
|
+
nonce: intent.nonce,
|
|
161
|
+
deadline: intent.deadline,
|
|
162
|
+
args: intent.callData,
|
|
163
|
+
});
|
|
164
|
+
// Encode the function call data
|
|
165
|
+
const callData = encodeFunctionData({
|
|
166
|
+
abi: gasStationAbi,
|
|
167
|
+
functionName: "execute",
|
|
168
|
+
args: [
|
|
169
|
+
intent.eoaAddress,
|
|
170
|
+
intent.outputContract,
|
|
171
|
+
intent.ethAmount,
|
|
172
|
+
packedData,
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
// Sign the transaction without sending it
|
|
176
|
+
const signedTx = await this.walletClient.signTransaction({
|
|
177
|
+
to: this.executionContract,
|
|
178
|
+
data: callData,
|
|
179
|
+
gas: BigInt(200000),
|
|
180
|
+
type: "eip1559",
|
|
181
|
+
account: this.walletClient.account,
|
|
182
|
+
chain: this.walletClient.chain,
|
|
183
|
+
});
|
|
184
|
+
return signedTx;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Execute a signed intent through the gas station contract.
|
|
188
|
+
* Packs the execution data according to the delegate contract's expected format and
|
|
189
|
+
* submits it via the execution contract.
|
|
190
|
+
* Call this with a paymaster client to submit and pay for the transaction.
|
|
191
|
+
*/
|
|
192
|
+
async execute(intent) {
|
|
193
|
+
// Pack the execution data (signature, nonce, deadline, args)
|
|
194
|
+
const packedData = packExecutionData({
|
|
195
|
+
signature: intent.signature,
|
|
196
|
+
nonce: intent.nonce,
|
|
197
|
+
deadline: intent.deadline,
|
|
198
|
+
args: intent.callData,
|
|
199
|
+
});
|
|
200
|
+
// Call the unified execute function with explicit parameters
|
|
201
|
+
const txHash = await this.walletClient.sendTransaction({
|
|
202
|
+
to: this.executionContract,
|
|
203
|
+
data: encodeFunctionData({
|
|
204
|
+
abi: gasStationAbi,
|
|
205
|
+
functionName: "execute",
|
|
206
|
+
args: [
|
|
207
|
+
intent.eoaAddress,
|
|
208
|
+
intent.outputContract,
|
|
209
|
+
intent.ethAmount,
|
|
210
|
+
packedData,
|
|
211
|
+
],
|
|
212
|
+
}),
|
|
213
|
+
gas: BigInt(200000),
|
|
214
|
+
account: this.walletClient.account,
|
|
215
|
+
});
|
|
216
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
217
|
+
hash: txHash,
|
|
218
|
+
});
|
|
219
|
+
if (receipt.status !== "success") {
|
|
220
|
+
// Try to get the revert reason if available
|
|
221
|
+
const revertReason = await this.getRevertReason(txHash);
|
|
222
|
+
throw new Error(`Execution failed: ${revertReason || "Transaction reverted"}. ` +
|
|
223
|
+
`Gas used: ${receipt.gasUsed}/${receipt.cumulativeGasUsed}. ` +
|
|
224
|
+
`Transaction hash: ${txHash}`);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
txHash,
|
|
228
|
+
blockNumber: receipt.blockNumber,
|
|
229
|
+
gasUsed: receipt.gasUsed,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { GasStationClient };
|
|
235
|
+
//# sourceMappingURL=gasStationClient.mjs.map
|