Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/gasless-custody-aa/.env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
API_PUBLIC_KEY="<Turnkey API Public Key (that starts with 02 or 03)>"
API_PRIVATE_KEY="<Turnkey API Private Key>"
BASE_URL="https://api.turnkey.com"
ORGANIZATION_ID="<Turnkey organization ID>"
SIGN_WITH="<Turnkey Wallet Account Address, Private Key Address, or Private Key ID>"
ZERODEV_RPC="<Zerodev RPC URL>" # see https://dashboard.zerodev.app/
OMNIBUS_ADDRESS="<Central hot wallet>"
TOKEN_ADDRESS="<ERC-20 token address (chain specific!)>"
80 changes: 80 additions & 0 deletions examples/gasless-custody-aa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Example: `gasless-custody`

This example shows how to construct and broadcast a transaction using Turnkey with [`Viem`](https://viem.sh/docs/clients/wallet.html), and [`Zerodev`](https://docs.zerodev.app/sdk/getting-started/quickstart).

## Getting started

### 1/ Cloning the example

Make sure you have `Node.js` installed locally; we recommend using Node v18+.

```bash
$ git clone https://github.com/tkhq/sdk
$ cd sdk/
$ corepack enable # Install `pnpm`
$ pnpm install -r # Install dependencies
$ pnpm run build-all # Compile source code
$ cd examples/gassless-custody-aa/
```

### 2a/ Setting up Turnkey

The first step is to set up your Turnkey organization and account. By following the [Quickstart](https://docs.turnkey.com/getting-started/quickstart) guide, you should have:

- A public/private API key pair for Turnkey
- An organization ID
- A Turnkey wallet account (address), private key address, or a private key ID


### 2b/ ZeroDev config

Create a ZeroDev account if you do not already have one. Visit the [ZeroDev Dashboard](https://dashboard.zerodev.app/), and from the Project view add your relevant networks and head to `Gas Policies`.
Choose a chain and tick `Sponsor all transactions`.
Finally, return to the Project's `General` tab and copy your Active Network's `Bundler/Paymaster RPC` URL.
You can find more details on the tutorial page here: https://docs.zerodev.app/sdk/getting-started/tutorial.

Once you've gathered these values, add them to a new `.env.local` file. Notice that your private key should be securely managed and **_never_** be committed to git.

```bash
$ cp .env.local.example .env.local
```

Now open `.env.local` and add the missing environment variables:

- `API_PUBLIC_KEY`
- `API_PRIVATE_KEY`
- `BASE_URL`
- `ORGANIZATION_ID`
- `SIGN_WITH` -- A Turnkey wallet account address, private key address, or private key ID.
- `ZERODEV_RPC`
- `OMNIBUS_ADDRESS`
- `TOKEN_ADDRESS`

Also ensure the token ABI populated in `src/token_abi.ts` is correct for your contract. ERC-20 is populated by default.
- `TOKEN_ABI`

### 3/ Running the scripts

These scripts construct transactions via Turnkey and broadcast them via Infura. If the scripts exit because your account isn't funded, you can request funds on https://sepoliafaucet.com/, via Coinbase Wallet, etc.

#### Viem

```bash
$ pnpm start
```

This script will do the following:

1. instantiate a Turnkey server client
2. generate a fresh Turnkey wallet, representing the user
3. create relevant Account and Client types for the user and signer
4. generate EIP-7702 Authorizations for both user and signer
5. instantiate ZeroDev types for these new smart accounts
6. wait for user to deposit a small amount of ERC-20 token to user wallet
7. constructs a gassless transaction, in this case a self-send to demonstrate functionality
8. automatically sweeps the balance from this new address to a provided OMNIBUS_ADDRESS

Check your terminal for more info!

Some of the flows demonstrated are covered more completely in other developer examples, such as [ZeroDev's demo](https://7702.zerodev.app/turnkey),
[`sweeper`](https://github.com/tkhq/sdk/tree/main/examples/sweeper), [`with-zerodev-aa`](https://github.com/tkhq/sdk/tree/main/examples/with-zerodev-aa), and further inspiration can be taken from [`with-eth-passkeys-galore`](https://github.com/tkhq/sdk/tree/main/examples/with-eth-passkeys-galore).
29 changes: 29 additions & 0 deletions examples/gasless-custody-aa/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@turnkey/example-gasless-custody-aa",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "tsx src/eip7702.ts",
"clean": "rimraf ./dist ./.cache",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@tanstack/react-query": "5.87.4",
"@turnkey/ethers": "workspace:*",
"@turnkey/sdk-server": "workspace:*",
"@turnkey/viem": "workspace:*",
"@zerodev/sdk": "5.5.2",
"dotenv": "16.0.3",
"ethers": "6.10.0",
"permissionless": "0.1.45",
"prompts": "2.4.2",
"typescript": "5.4.3",
"viem": "2.37.8",
"wagmi": "2.16.9"
},
"devDependencies": {
"@types/node": "22.7.7",
"@types/prompts": "2.4.2",
"tslib": "2.8.0"
}
}
49 changes: 49 additions & 0 deletions examples/gasless-custody-aa/src/createNewWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Turnkey as TurnkeySDKServer } from "@turnkey/sdk-server";

import * as crypto from "crypto";
import { refineNonNull } from "./util";

export async function createNewWallet() {
console.log("creating a new wallet on Turnkey...\n");

const walletName = `ETH Wallet ${crypto.randomBytes(2).toString("hex")}`;

try {
const turnkeyClient = new TurnkeySDKServer({
apiBaseUrl: "https://api.turnkey.com",
apiPublicKey: process.env.API_PUBLIC_KEY!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});

const { walletId, addresses } = await turnkeyClient
.apiClient()
.createWallet({
walletName,
accounts: [
{
curve: "CURVE_SECP256K1",
pathFormat: "PATH_FORMAT_BIP32",
path: "m/44'/60'/0'/0/0",
addressFormat: "ADDRESS_FORMAT_ETHEREUM",
},
],
});

const newWalletId = refineNonNull(walletId);
const address = refineNonNull(addresses[0]);

// Success!
console.log(
[
`New Ethereum wallet created!`,
`- Name: ${walletName}`,
`- Wallet ID: ${newWalletId}`,
`- Address: ${address}`,
].join("\n"),
);
return addresses[0];
} catch (error) {
throw new Error("Failed to create a new Ethereum wallet: " + error);
}
}
Loading
Loading