
Research
Namastex.ai npm Packages Hit with TeamPCP-Style CanisterWorm Malware
Malicious Namastex.ai npm packages appear to replicate TeamPCP-style Canister Worm tradecraft, including exfiltration and self-propagation.
@turnkey/gas-station
Advanced tools
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.
This SDK enables you to:
Perfect for building dApps where users don't need ETH for gas, enabling seamless onboarding and better UX.
pnpm install @turnkey/gas-station @turnkey/sdk-server @turnkey/viem viem
Create .env.local:
# Turnkey Configuration
BASE_URL=https://api.turnkey.com
API_PRIVATE_KEY=your_turnkey_api_private_key
API_PUBLIC_KEY=your_turnkey_api_public_key
ORGANIZATION_ID=your_turnkey_organization_id
# Wallet Addresses
EOA_ADDRESS=0x... # User's wallet address
PAYMASTER_ADDRESS=0x... # Your paymaster address
# RPC Configuration
BASE_RPC_URL=https://mainnet.base.org
ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/...
# Gas Station Contracts (Optional - defaults to deterministic addresses)
DELEGATE_CONTRACT=0x... # EIP-7702 delegate contract
EXECUTION_CONTRACT=0x... # Gas Sponsorship entrypoint contract which calls the delegate.
Note: The gas station contracts are currently deployed at deterministic addresses on the following chains:
These addresses are built into the SDK, so you don't need to specify them unless you are using custom deployments.
import { GasStationClient } from "@turnkey/gas-station";
import { Turnkey } from "@turnkey/sdk-server";
import { createAccount } from "@turnkey/viem";
import { parseEther, parseUnits, createWalletClient, http } from "viem";
import { base } from "viem/chains";
// Initialize Turnkey
const turnkeyClient = new Turnkey({
apiBaseUrl: process.env.BASE_URL!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
apiPublicKey: process.env.API_PUBLIC_KEY!,
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
// Create Turnkey accounts
const userAccount = await createAccount({
client: turnkeyClient.apiClient(),
organizationId: process.env.ORGANIZATION_ID!,
signWith: process.env.EOA_ADDRESS as `0x${string}`,
});
const paymasterAccount = await createAccount({
client: turnkeyClient.apiClient(),
organizationId: process.env.ORGANIZATION_ID!,
signWith: process.env.PAYMASTER_ADDRESS as `0x${string}`,
});
// Create viem wallet clients
const userWalletClient = createWalletClient({
account: userAccount,
chain: base,
transport: http(process.env.BASE_RPC_URL!),
});
const paymasterWalletClient = createWalletClient({
account: paymasterAccount,
chain: base,
transport: http(process.env.BASE_RPC_URL!),
});
// Create Gas Station clients
const userClient = new GasStationClient({
walletClient: userWalletClient,
});
const paymasterClient = new GasStationClient({
walletClient: paymasterWalletClient,
});
// One-time: Authorize the EOA to use gas station
const authorization = await userClient.signAuthorization();
await paymasterClient.submitAuthorizations([authorization]);
// Execute a gasless ETH transfer
let nonce = await userClient.getNonce();
const ethIntent = await userClient
.createIntent()
.transferETH("0xRecipient...", parseEther("0.1"))
.sign(nonce);
await paymasterClient.execute(ethIntent);
// Execute a gasless token transfer
nonce = await userClient.getNonce();
const usdcIntent = await userClient
.createIntent()
.transferToken(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
"0xRecipient...",
parseUnits("10", 6),
)
.sign(nonce);
await paymasterClient.execute(usdcIntent);
Main client for gas station operations. Each client instance wraps a viem wallet client.
new GasStationClient({
walletClient: WalletClient, // Viem wallet client (e.g., with Turnkey account)
delegateContract?: `0x${string}`, // Optional: defaults to deterministic address
executionContract?: `0x${string}`, // Optional: defaults to deterministic address
})
End User Methods (call with user client):
signAuthorization(): Promise<SignedAuthorization>
createIntent(): IntentBuilder
getNonce(address?: Address): Promise<bigint>
Paymaster Methods (call with paymaster client):
submitAuthorizations(authorizations: SignedAuthorization[]): Promise<{ txHash, blockNumber }>
execute(intent: ExecutionIntent): Promise<{ txHash, blockNumber, gasUsed }>
Composable builder for complex multi-step transactions.
const nonce = await userClient.getNonce();
const builder = userClient.createIntent();
const intent = await builder
.transferToken(usdcAddress, recipient, amount)
.sign(nonce);
await paymasterClient.execute(intent);
// Gasless USDC payment
const nonce = await userClient.getNonce();
const intent = await userClient
.createIntent()
.transferToken(usdcAddress, recipientAddress, parseUnits("50", 6))
.sign(nonce);
const result = await paymasterClient.execute(intent);
console.log(`Payment sent: ${result.txHash}`);
// Step 1: Approve DEX to spend tokens
let nonce = await userClient.getNonce();
const approvalIntent = await userClient
.createIntent()
.approveToken(usdcAddress, dexAddress, parseUnits("100", 6))
.sign(nonce);
await paymasterClient.execute(approvalIntent);
// Step 2: Execute swap
nonce = await userClient.getNonce();
const swapIntent = await userClient
.createIntent()
.callContract({
contract: dexAddress,
abi: DEX_ABI,
functionName: "swapExactTokensForTokens",
args: [amountIn, amountOutMin, path, recipient, deadline],
})
.sign(nonce);
await paymasterClient.execute(swapIntent);
async function onboardUser(userAddress: string) {
// Create viem wallet client for user
const userAccount = await createAccount({
client: turnkeyClient.apiClient(),
organizationId: ORGANIZATION_ID,
signWith: userAddress as `0x${string}`,
});
const userWalletClient = createWalletClient({
account: userAccount,
chain: base,
transport: http(BASE_RPC_URL),
});
// Create Gas Station clients
const userClient = new GasStationClient({
walletClient: userWalletClient,
});
// Authorize user (paymaster pays)
const authorization = await userClient.signAuthorization();
await paymasterClient.submitAuthorizations([authorization]);
// User can now execute transactions without ETH
console.log("✅ User ready for gasless transactions!");
}
User (EOA)
↓ Signs EIP-712 intent off-chain
↓
SDK (GasStationClient)
↓ Builds transaction
↓
Paymaster
↓ Submits transaction, pays gas
↓
Gas Station Contract
↓ Validates signature & nonce
↓ Executes on behalf of EOA
↓
Target Contract (USDC, NFT, DEX, etc.)
Available presets for quick setup:
// Chain presets are available for quick configuration
import { CHAIN_PRESETS, GasStationClient } from "@turnkey/gas-station";
import { createWalletClient, http } from "viem";
const basePreset = CHAIN_PRESETS.BASE_MAINNET;
const userWalletClient = createWalletClient({
account: userAccount,
chain: basePreset.chain,
transport: http(basePreset.rpcUrl),
});
const userClient = new GasStationClient({
walletClient: userWalletClient,
});
Turnkey policies provide additional security layers by restricting what transactions can be signed and executed. The Gas Station SDK includes helpers for creating these policies.
Restrict what EIP-712 intents the EOA can sign:
import { buildIntentSigningPolicy } from "@turnkey/gas-station";
// USDC-only policy
const eoaPolicy = buildIntentSigningPolicy({
organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
restrictions: {
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC on Base
disallowEthTransfer: true, // Disallow ETH transfers
},
policyName: "USDC Only Policy",
});
// Resulting policy restricts signing to USDC transfers only:
// {
// organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
// policyName: "USDC Only Policy",
// effect: "EFFECT_ALLOW",
// consensus: "approvers.any(user, user.id == '3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a')",
// condition: "activity.resource == 'PRIVATE_KEY' && " +
// "activity.action == 'SIGN' && " +
// "eth.eip_712.primary_type == 'Execution' && " +
// "(eth.eip_712.message['to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
// "eth.eip_712.message['value'] == '0'",
// notes: "Restricts which EIP-712 intents the EOA can sign for gas station execution"
// }
Restrict what on-chain transactions the paymaster can submit:
import {
buildPaymasterExecutionPolicy,
DEFAULT_EXECUTION_CONTRACT,
ensureGasStationInterface,
} from "@turnkey/gas-station";
import { parseGwei, parseEther } from "viem";
// First, ensure the Gas Station ABI is uploaded (enables ABI-based policies)
await ensureGasStationInterface(
turnkeyClient.apiClient(),
"your-org-id",
DEFAULT_EXECUTION_CONTRACT,
undefined,
"Base Mainnet",
);
// Paymaster protection policy with ETH amount limit
const paymasterPolicy = buildPaymasterExecutionPolicy({
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
restrictions: {
allowedEOAs: ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"],
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
maxEthAmount: parseEther("0.1"), // Max 0.1 ETH per EOA transaction
maxGasPrice: parseGwei("50"), // Max 50 gwei gas price
maxGasLimit: 500000n, // Max 500k gas limit
},
policyName: "Paymaster Protection",
});
// Resulting policy uses ABI parsing for direct argument access:
// {
// organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
// policyName: "Paymaster Protection",
// effect: "EFFECT_ALLOW",
// consensus: "approvers.any(user, user.id == '8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c')",
// condition: "activity.resource == 'PRIVATE_KEY' && " +
// "activity.action == 'SIGN' && " +
// "eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c' && " +
// "(eth.tx.contract_call_args['_to'] == '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') && " +
// "(eth.tx.contract_call_args['_target'] == '0x742d35cc6634c0532925a3b844bc9e7595f0beb') && " +
// "eth.tx.contract_call_args['_ethAmount'] <= 100000000000000000 && " +
// "eth.tx.gasPrice <= 50000000000 && " +
// "eth.tx.gas <= 500000",
// notes: "Restricts which transactions the paymaster can execute on the gas station"
// }
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.
Combine both policy types for maximum security:
import {
buildIntentSigningPolicy,
buildPaymasterExecutionPolicy,
DEFAULT_EXECUTION_CONTRACT,
} from "@turnkey/gas-station";
import { parseGwei } from "viem";
// Layer 1: EOA can only sign USDC intents
const eoaPolicy = buildIntentSigningPolicy({
organizationId: "a5b89e4f-1234-5678-9abc-def012345678",
eoaUserId: "3c7d6e8a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
restrictions: {
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
disallowEthTransfer: true, // No ETH transfers
},
});
// Layer 2: Paymaster can only execute for specific users with gas limits
const paymasterPolicy = buildPaymasterExecutionPolicy({
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
paymasterUserId: "8f2a1b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
restrictions: {
allowedEOAs: ["0xUserAddress..."],
allowedContracts: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
maxGasPrice: parseGwei("50"),
maxGasLimit: 500000n,
},
});
When using buildPaymasterExecutionPolicy, the SDK creates Turnkey policies that parse the transaction calldata to enforce restrictions. Understanding the transaction structure allows you to write custom policies for advanced use cases.
When the paymaster signs an execution transaction calling execute(address _target, address _to, uint256 _ethAmount, bytes _data), the transaction data (eth.tx.data) has the following structure:
| Position in eth.tx.data | Length | Content | Example |
|---|---|---|---|
[2..10] | 8 chars | Function selector | 6c5c2ed9 (execute) |
[10..74] | 64 chars | _target (EOA, padded) | 0000...742d35cc6634c0532925a3b844bc9e7595f0beb |
[74..138] | 64 chars | _to (output contract) | 0000...833589fcd6edb6e08f4c7c32d4f71b54bda02913 |
[138..202] | 64 chars | _ethAmount (uint256) | 0000...0000 (0 ETH) or amount in wei |
[202..266] | 64 chars | Offset to _data bytes | 0000...0080 (128 bytes) |
[266..330] | 64 chars | Packed data length | 0000...0055 (85 bytes: 65+16+4) |
[330..460] | 130 chars | Signature (65 bytes) | EIP-712 signature from EOA |
[460..492] | 32 chars | Nonce (16 bytes) | 00000000000000000000000000000000 |
[492..500] | 8 chars | Deadline (4 bytes) | 6ac7d340 (Unix timestamp) |
[500+] | Variable | Call data | Encoded function call for target contract |
Important: Turnkey's eth.tx.data includes the 0x prefix, so positions start at index 2 (after 0x).
Note: The deadline is a Unix timestamp that prevents replay attacks by expiring signatures after a specified time. The SDK defaults to 1 hour, customizable with withDeadline().
Check execution contract address:
eth.tx.to == "0x00000000008c57a1ce37836a5e9d36759d070d8c";
Check which EOA is executing:
eth.tx.data[10..74] == '0000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb'
Check target contract (output contract):
eth.tx.data[74..138] == '0000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913'
Check ETH amount:
eth.tx.data[138..202].hex_to_uint() <= 100000000000000000; // Max 0.1 ETH in wei
Check gas price:
eth.tx.gasPrice <= 50000000000; // 50 gwei in wei
Check gas limit:
eth.tx.gas <= 500000;
Allow paymaster to execute for USDC or DAI only:
const policy = {
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
policyName: "Stablecoin Execution Policy",
effect: "EFFECT_ALLOW",
consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
condition: [
"activity.resource == 'PRIVATE_KEY'",
"activity.action == 'SIGN'",
"eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
// Allow USDC or DAI
"(eth.tx.data[74..138] == '0000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913' || eth.tx.data[74..138] == '00000000000000000000006b175474e89094c44da98b954eedeac495271d0f')",
// Gas limits
"eth.tx.gasPrice <= 100000000000",
"eth.tx.gas <= 500000",
].join(" && "),
notes: "Allow USDC and DAI execution with gas limits",
};
await turnkeyClient.apiClient().createPolicy(policy);
Only allow execution for approved user wallets:
const approvedEOAs = [
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"0x1234567890123456789012345678901234567890",
];
const eoaConditions = approvedEOAs
.map((addr) => {
const padded = addr.slice(2).toLowerCase().padStart(64, "0");
return `eth.tx.data[10..74] == '${padded}'`;
})
.join(" || ");
const policy = {
organizationId: "f8c3a5e7-9876-5432-1abc-def098765432",
policyName: "Approved Users Only",
effect: "EFFECT_ALLOW",
consensus: `approvers.any(user, user.id == '${paymasterUserId}')`,
condition: [
"activity.resource == 'PRIVATE_KEY'",
"activity.action == 'SIGN'",
"eth.tx.to == '0x00000000008c57a1ce37836a5e9d36759d070d8c'",
`(${eoaConditions})`,
].join(" && "),
};
await turnkeyClient.apiClient().createPolicy(policy);
For most cases, use the built-in helpers which handle the byte positions correctly:
import {
buildPaymasterExecutionPolicy,
DEFAULT_EXECUTION_CONTRACT,
} from "@turnkey/gas-station";
import { parseGwei } from "viem";
const policy = buildPaymasterExecutionPolicy({
organizationId: subOrgId,
paymasterUserId: paymasterUserId,
executionContractAddress: DEFAULT_EXECUTION_CONTRACT,
restrictions: {
allowedContracts: [
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", // DAI
],
allowedEOAs: ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"],
maxGasPrice: parseGwei("100"),
maxGasLimit: 500000n,
},
policyName: "Production Paymaster Policy",
});
await turnkeyClient.apiClient().createPolicy(policy);
The helper functions automatically:
0x prefix)isAuthorized())authorize() once per EOASee the main SDK repository for license information.
FAQs
Turnkey Gas Station SDK for EIP-7702 delegated execution
We found that @turnkey/gas-station demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Malicious Namastex.ai npm packages appear to replicate TeamPCP-style Canister Worm tradecraft, including exfiltration and self-propagation.

Product
Explore exportable charts for vulnerabilities, dependencies, and usage with Reports, Socket’s new extensible reporting framework.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.