Skip to content

Commit a1fa36b

Browse files
Merge pull-request #1089
2 parents 54a6cd8 + 9997e60 commit a1fa36b

File tree

6 files changed

+117
-121
lines changed

6 files changed

+117
-121
lines changed

.changeset/chubby-peas-sip.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@turnkey/gas-station": major
3+
---
4+
5+
Updated the `@turnkey/gas-station` SDK to align with the audited smart contract changes. The audit resulted in several interface updates:
6+
7+
**Contract Changes:**
8+
- **New contract addresses**: Updated both delegate and execution contract addresses to the newly deployed versions
9+
- **EIP-712 field name changes**: The canonical delegate contract interface uses simplified field names (`to`, `value`, `data`) instead of the previous descriptive names (`outputContract`, `ethAmount`, `arguments`)
10+
11+
**SDK Updates:**
12+
- Updated `DEFAULT_EXECUTION_CONTRACT` address from `0x4ece92b06C7d2d99d87f052E0Fca47Fb180c3348` to `0x00000000008c57a1CE37836a5e9d36759D070d8c`
13+
- Updated `DEFAULT_DELEGATE_CONTRACT` address from `0xC2a37Ee08cAc3778d9d05FF0a93FD5B553C77E3a` to `0x000066a00056CD44008768E2aF00696e19A30084`
14+
- Updated EIP-712 Execution typehash field names to match the contract's canonical interface
15+
- Updated EIP-712 ApproveThenExecute typehash field names to match the contract's canonical interface
16+
- Updated Turnkey policy conditions in `buildIntentSigningPolicy` to reference the new field names (`to`, `value` instead of `outputContract`, `ethAmount`)
17+
- Updated documentation and examples to reflect the new field names
18+
19+
**Files Modified:**
20+
- `packages/gas-station/src/config.ts` - Updated contract addresses
21+
- `packages/gas-station/src/intentBuilder.ts` - Updated EIP-712 type definitions and message objects
22+
- `packages/gas-station/src/policyUtils.ts` - Updated policy condition field references and documentation

packages/gas-station/README.md

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Turnkey Gas Station SDK
22

3-
> **⚠️ BETA WARNING**: This SDK is currently in beta. The underlying smart contracts are **unaudited** and should not be used in production environments. Use at your own risk.
4-
53
A reusable SDK for implementing gasless transactions using EIP-7702, Turnkey wallet management, and your own paymaster. This package provides clean abstractions and utility methods to quickly integrate with Turnkey's contracts for sponsored transaction execution.
64

75
## What is This?
@@ -345,8 +343,8 @@ import { buildIntentSigningPolicy } from "@turnkey/gas-station";
345343

346344
// USDC-only policy
347345
const eoaPolicy = buildIntentSigningPolicy({
348-
organizationId: "your-org-id",
349-
eoaUserId: "user-id",
346+
organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
347+
eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
350348
restrictions: {
351349
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC on Base
352350
disallowEthTransfer: true, // Disallow ETH transfers
@@ -356,15 +354,15 @@ const eoaPolicy = buildIntentSigningPolicy({
356354

357355
// Resulting policy restricts signing to USDC transfers only:
358356
// {
359-
// organizationId: "your-org-id",
357+
// organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
360358
// policyName: "USDC Only Policy",
361359
// effect: "EFFECT_ALLOW",
362-
// consensus: "approvers.any(user, user.id == 'user-id')",
360+
// consensus: "approvers.any(user, user.id == '3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a')",
363361
// condition: "activity.resource == 'PRIVATE_KEY' && " +
364362
// "activity.action == 'SIGN' && " +
365363
// "eth.eip_712.primary_type == 'Execution' && " +
366-
// "(eth.eip_712.message['outputContract'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
367-
// "eth.eip_712.message['ethAmount'] == '0'",
364+
// "(eth.eip_712.message['to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
365+
// "eth.eip_712.message['value'] == '0'",
368366
// notes: "Restricts which EIP-712 intents the EOA can sign for gas station execution"
369367
// }
370368
```
@@ -392,8 +390,8 @@ await ensureGasStationInterface(
392390

393391
// Paymaster protection policy with ETH amount limit
394392
const paymasterPolicy = buildPaymasterExecutionPolicy({
395-
organizationId: "paymaster-org-id",
396-
paymasterUserId: "paymaster-user-id",
393+
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
394+
paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
397395
executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
398396
restrictions: {
399397
allowedEOAs: ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"],
@@ -407,23 +405,23 @@ const paymasterPolicy = buildPaymasterExecutionPolicy({
407405

408406
// Resulting policy uses ABI parsing for direct argument access:
409407
// {
410-
// organizationId: "paymaster-org-id",
408+
// organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
411409
// policyName: "Paymaster Protection",
412410
// effect: "EFFECT_ALLOW",
413-
// consensus: "approvers.any(user, user.id == 'paymaster-user-id')",
411+
// consensus: "approvers.any(user, user.id == '8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c')",
414412
// condition: "activity.resource == 'PRIVATE_KEY' && " +
415413
// "activity.action == 'SIGN' && " +
416-
// "eth.tx.to == '0xe511ad0a281c10b8408381e2ab8525abe587827b' && " +
414+
// "eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c' && " +
417415
// "(eth.tx.contract_call_args['_to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
418-
// "(eth.tx.contract_call_args['_targetEoA'] == '0x742d35cc6634c0532925a3b844bc9e7595f0beb') && " +
419-
// "eth.tx.contract_call_args['ethAmount'] <= 100000000000000000 && " +
416+
// "(eth.tx.contract_call_args['_target'] == '0x742d35cc6634c0532925a3b844bc9e7595f0beb') && " +
417+
// "eth.tx.contract_call_args['_ethAmount'] <= 100000000000000000 && " +
420418
// "eth.tx.gasPrice <= 50000000000 && " +
421419
// "eth.tx.gas <= 500000",
422420
// notes: "Restricts which transactions the paymaster can execute on the gas station"
423421
// }
424422
```
425423

426-
**Note:** The `ensureGasStationInterface()` function uploads the Gas Station ABI to Turnkey's Smart Contract Interface feature. This enables Turnkey's policy engine to parse the ABI-encoded transaction data and directly compare the `ethAmount` parameter as a uint256 value, rather than raw bytes. The function checks if the ABI already exists before uploading to avoid duplicates.
424+
**Note:** The `ensureGasStationInterface()` function uploads the Gas Station ABI to Turnkey's Smart Contract Interface feature. This enables Turnkey's policy engine to parse the ABI-encoded transaction data and directly compare the `_ethAmount` parameter as a uint256 value, rather than raw bytes. The function checks if the ABI already exists before uploading to avoid duplicates.
427425

428426
#### Defense in Depth
429427

@@ -439,8 +437,8 @@ import { parseGwei } from "viem";
439437

440438
// Layer 1: EOA can only sign USDC intents
441439
const eoaPolicy = buildIntentSigningPolicy({
442-
organizationId: "user-org",
443-
eoaUserId: "user-id",
440+
organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
441+
eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
444442
restrictions: {
445443
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
446444
disallowEthTransfer: true, // No ETH transfers
@@ -449,8 +447,8 @@ const eoaPolicy = buildIntentSigningPolicy({
449447

450448
// Layer 2: Paymaster can only execute for specific users with gas limits
451449
const paymasterPolicy = buildPaymasterExecutionPolicy({
452-
organizationId: "paymaster-org",
453-
paymasterUserId: "paymaster-user-id",
450+
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
451+
paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
454452
executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
455453
restrictions: {
456454
allowedEOAs: ["0xUserAddress..."],
@@ -491,7 +489,7 @@ When the paymaster signs an execution transaction calling `execute(address _targ
491489
**Check execution contract address:**
492490

493491
```typescript
494-
eth.tx.to == "0x576a4d741b96996cc93b4919a04c16545734481f";
492+
eth.tx.to == "0x00000000008c57a1ce37836a5e9d36759d070d8c";
495493
```
496494

497495
**Check which EOA is executing:**
@@ -530,14 +528,14 @@ Allow paymaster to execute for USDC or DAI only:
530528

531529
```typescript
532530
const policy = {
533-
organizationId: "paymaster-org-id",
531+
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
534532
policyName: "Stablecoin Execution Policy",
535533
effect: "EFFECT_ALLOW",
536534
consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
537535
condition: [
538536
"activity.resource == 'PRIVATE_KEY'",
539537
"activity.action == 'SIGN'",
540-
"eth.tx.to == '0x576a4d741b96996cc93b4919a04c16545734481f'",
538+
"eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
541539
// Allow USDC or DAI
542540
"(eth.tx.data[74..138] == '0000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913' || eth.tx.data[74..138] == '00000000000000000000006b175474e89094c44da98b954eedeac495271d0f')",
543541
// Gas limits
@@ -568,14 +566,14 @@ const eoaConditions = approvedEOAs
568566
.join(" || ");
569567

570568
const policy = {
571-
organizationId: "paymaster-org-id",
569+
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
572570
policyName: "Approved Users Only",
573571
effect: "EFFECT_ALLOW",
574572
consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
575573
condition: [
576574
"activity.resource == 'PRIVATE_KEY'",
577575
"activity.action == 'SIGN'",
578-
"eth.tx.to == '0x576a4d741b96996cc93b4919a04c16545734481f'",
576+
"eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
579577
`(${eoaConditions})`,
580578
].join(" && "),
581579
};

packages/gas-station/src/abi/gas-station.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ export const gasStationAbi = [
88
},
99
{ inputs: [], name: "ExecutionFailed", type: "error" },
1010
{ inputs: [], name: "InvalidFunctionSelector", type: "error" },
11-
{ inputs: [], name: "NoEthAllowed", type: "error" },
1211
{ inputs: [], name: "NotDelegated", type: "error" },
1312
{ stateMutability: "nonpayable", type: "fallback" },
13+
{
14+
inputs: [],
15+
name: "TK_GAS_DELEGATE",
16+
outputs: [{ internalType: "address", name: "", type: "address" }],
17+
stateMutability: "view",
18+
type: "function",
19+
},
1420
{
1521
inputs: [
1622
{ internalType: "address", name: "_target", type: "address" },
@@ -152,26 +158,6 @@ export const gasStationAbi = [
152158
stateMutability: "view",
153159
type: "function",
154160
},
155-
{
156-
inputs: [
157-
{ internalType: "address", name: "_targetEoA", type: "address" },
158-
{ internalType: "uint128", name: "_nonce", type: "uint128" },
159-
{
160-
components: [
161-
{ internalType: "address", name: "to", type: "address" },
162-
{ internalType: "uint256", name: "value", type: "uint256" },
163-
{ internalType: "bytes", name: "data", type: "bytes" },
164-
],
165-
internalType: "struct IBatchExecution.Call[]",
166-
name: "_calls",
167-
type: "tuple[]",
168-
},
169-
],
170-
name: "hashBatchExecution",
171-
outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }],
172-
stateMutability: "view",
173-
type: "function",
174-
},
175161
{
176162
inputs: [
177163
{ internalType: "address", name: "_targetEoA", type: "address" },
@@ -207,7 +193,6 @@ export const gasStationAbi = [
207193
inputs: [
208194
{ internalType: "address", name: "_targetEoA", type: "address" },
209195
{ internalType: "uint128", name: "_counter", type: "uint128" },
210-
{ internalType: "address", name: "_sender", type: "address" },
211196
],
212197
name: "hashBurnSessionCounter",
213198
outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }],
@@ -248,13 +233,6 @@ export const gasStationAbi = [
248233
stateMutability: "view",
249234
type: "function",
250235
},
251-
{
252-
inputs: [],
253-
name: "tkGasDelegate",
254-
outputs: [{ internalType: "address", name: "", type: "address" }],
255-
stateMutability: "view",
256-
type: "function",
257-
},
258236
{
259237
inputs: [
260238
{ internalType: "address", name: "_targetEoA", type: "address" },
@@ -266,5 +244,4 @@ export const gasStationAbi = [
266244
stateMutability: "view",
267245
type: "function",
268246
},
269-
{ stateMutability: "payable", type: "receive" },
270247
] as const;

packages/gas-station/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { base, mainnet, sepolia } from "viem/chains";
44

55
// Default contract addresses (deterministically deployed across all chains)
66
export const DEFAULT_DELEGATE_CONTRACT: Hex =
7-
"0xC2a37Ee08cAc3778d9d05FF0a93FD5B553C77E3a";
7+
"0x000066a00056CD44008768E2aF00696e19A30084";
88
export const DEFAULT_EXECUTION_CONTRACT: Hex =
9-
"0x4ece92b06C7d2d99d87f052E0Fca47Fb180c3348";
9+
"0x00000000008c57a1CE37836a5e9d36759D070d8c";
1010

1111
// Type definitions
1212
export interface GasStationConfig {

packages/gas-station/src/intentBuilder.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -145,23 +145,23 @@ export class IntentBuilder {
145145
verifyingContract: this.config.eoaAddress,
146146
};
147147

148-
// Original: keccak256("Execution(uint128 nonce,uint32 deadline,address outputContract,uint256 ethAmount,bytes arguments)")
148+
// keccak256("Execution(uint128 nonce,uint32 deadline,address to,uint256 value,bytes data)")
149149
const types = {
150150
Execution: [
151151
{ name: "nonce", type: "uint128" },
152152
{ name: "deadline", type: "uint32" },
153-
{ name: "outputContract", type: "address" },
154-
{ name: "ethAmount", type: "uint256" },
155-
{ name: "arguments", type: "bytes" },
153+
{ name: "to", type: "address" },
154+
{ name: "value", type: "uint256" },
155+
{ name: "data", type: "bytes" },
156156
],
157157
};
158158

159159
const message = {
160160
nonce,
161161
deadline,
162-
outputContract: this.outputContract,
163-
ethAmount: this.ethAmount,
164-
arguments: this.callData,
162+
to: this.outputContract,
163+
value: this.ethAmount,
164+
data: this.callData,
165165
};
166166

167167
const signature = await this.config.eoaWalletClient.signTypedData({
@@ -213,17 +213,17 @@ export class IntentBuilder {
213213
};
214214

215215
// Based on hashApproveThenExecute from the contract
216-
// keccak256("ApproveThenExecute(uint128 nonce,uint32 deadline,address erc20Contract,address spender,uint256 approveAmount,address outputContract,uint256 ethAmount,bytes arguments)")
216+
// keccak256("ApproveThenExecute(uint128 nonce,uint32 deadline,address erc20Contract,address spender,uint256 approveAmount,address to,uint256 value,bytes data)")
217217
const types = {
218218
ApproveThenExecute: [
219219
{ name: "nonce", type: "uint128" },
220220
{ name: "deadline", type: "uint32" },
221221
{ name: "erc20Contract", type: "address" },
222222
{ name: "spender", type: "address" },
223223
{ name: "approveAmount", type: "uint256" },
224-
{ name: "outputContract", type: "address" },
225-
{ name: "ethAmount", type: "uint256" },
226-
{ name: "arguments", type: "bytes" },
224+
{ name: "to", type: "address" },
225+
{ name: "value", type: "uint256" },
226+
{ name: "data", type: "bytes" },
227227
],
228228
};
229229

@@ -233,9 +233,9 @@ export class IntentBuilder {
233233
erc20Contract: erc20Address,
234234
spender,
235235
approveAmount,
236-
outputContract: this.outputContract,
237-
ethAmount: this.ethAmount,
238-
arguments: this.callData,
236+
to: this.outputContract,
237+
value: this.ethAmount,
238+
data: this.callData,
239239
};
240240

241241
const signature = await this.config.eoaWalletClient.signTypedData({

0 commit comments

Comments
 (0)