Skip to content

Solana Gasless docs tweak #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
137 changes: 116 additions & 21 deletions concepts/policies/delegated-access.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@ description: "With Turnkey you can create multi-user accounts with flexible co-o

## Overview

Delegated access works by creating a specialized business-controlled user within each end-user’s sub-organization that has carefully scoped permissions to perform only specific actions, such as signing transactions to designated addresses. This can enable your backend to do things like:
* Automate common transactions (e.g., staking, redemptions)
* Sign transactions to whitelisted addresses without user involvement
* Perform scheduled operations
* Respond to specific onchain events programmatically
Delegated access works by creating a specialized business-controlled user within each end-user’s sub-organization that has carefully scoped permissions to perform only specific actions, such as signing transactions to designated addresses. This can enable your backend to do things like:

## Implementation flow
- Automate common transactions (e.g., staking, redemptions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious, did you manually make any of these formatting changes? guessing it's just the result of modifying some files via Mintlify UI (no big deal either way!)

- Sign transactions to whitelisted addresses without user involvement
- Perform scheduled operations
- Respond to specific onchain events programmatically

## Implementation flow

Here’s how to implement delegated access for an embedded wallet setup:
* Create a sub-organization with two root users: The end user and your “Delegated User” with an API key authenticator that you control
* Enable the Delegated Account to take particular actions by setting policies explicitly allowing those specific actions
* Update the root quorum to ensure only the end-user retains root privileges

- Create a sub-organization with two root users: The end user and your “Delegated User” with an API key authenticator that you control
- Enable the Delegated Account to take particular actions by setting policies explicitly allowing those specific actions
- Update the root quorum to ensure only the end-user retains root privileges

A simple example demonstrating the delegated acess setup can be found [here](https://github.com/tkhq/sdk/tree/main/examples/delegated-access).

## Step-by-step implementation

### Step 1: Create a sub-organization with two root users[​](#step-1-create-a-sub-organization-with-two-root-users)

### Step 1: Create a sub-organization with two root users[​](#step-1-create-a-sub-organization-with-two-root-users "Direct link to Step 1: Create a sub-organization with two root users")

* Create your sub-organization with the two root users being:

* The end-user
* A user you control (we'll call it the ‘Delegated Account’)
- Create your sub-organization with the two root users being:
- The end-user
- A user you control (we'll call it the ‘Delegated Account’)

```json
{
Expand Down Expand Up @@ -87,12 +87,11 @@ A simple example demonstrating the delegated acess setup can be found [here](htt

### Step 2: Limit the permissions of the Delegated Account user via policies

* Create a custom policy granting the Delegated Account specific permissions. You might grant that user permissions to:

* Sign any transaction
* Sign only transactions to a specific address
* Create new users in the sub-org
* Or any other activity you want to be able to take using your Delegated Account
- Create a custom policy granting the Delegated Account specific permissions. You might grant that user permissions to:
- Sign any transaction
- Sign only transactions to a specific address
- Create new users in the sub-org
- Or any other activity you want to be able to take using your Delegated Account

Here’s one example, granting the Delegated Account only the permission to sign ethereum transactions to a specific receiver address:

Expand Down Expand Up @@ -232,3 +231,99 @@ dotenv.config();
userIds: [subOrg.rootUserIds[1]], // retain the end user
});
```

## Frequently Asked Questions

### Policy Design and Creation

<Accordion title="Can I create policies on behalf of a user without their explicit approval?">
Yes — if the delegated access (DA) user is part of the root quorum, they can create policies unilaterally. Once removed from the root quorum, only the remaining quorum member (typically the end-user) can make further policy changes.
</Accordion>

<Accordion title="When does the user 'approve' a delegated access setup?">
End-user approval typically happens during **<u>intent</u>** creation — for example, when the end-user authorizes a DA user by adjusting the quorum or signs an initial setup transaction.
</Accordion>

<Accordion title="After a limit order is filled, how can I remove/null a policy programmatically?">
As long as the DA user remains authorized, they can remove policies programmatically. If they’ve been removed from the quorum, policy deletion will require the user’s explicit approval.

**<u>NOTE:</u>** Turnkey is looking to support the concept of 'one-time-use policies' to make it easier to manage redundany policies.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: redundany --> redundant

</Accordion>

### Security & Risk Management

<Accordion title="If a delegated API key is leaked, does that allow someone to act on behalf of the user?">
Yes — if the key is attached to a broad policy. That’s why it’s important to limit the scope of policies and enforce API hygiene practices.
</Accordion>

<Accordion title="Is this the same risk as having a master delegate account?">
In effect, yes. The key difference is that granular policies **can restrict** what a DA user can do, offering better security hygiene even if there's still elevated access.
</Accordion>

<Accordion title="What are best practices for storing and rotating Delegate Access API keys?">
Typically this is a combination of, or all of the following practices, though not exclusive to just these:

- Using short-lived keys whenever viable
- Rotating API keys regurarly
- Monitoring the usage
- Secure storage (e.g. in HSMs or vaults)
</Accordion>

### Best Practices

<Accordion title="How do I scope a delegated access policy to reduce signing risk?">
You can define strict transaction conditions. For example:

```javascript
solana.tx.instructions.count() == 1 &&
solana.tx.transfers.count() == 1 &&
solana.tx.transfers.all(transfer, transfer.to == '<SPECIFIC_ADDRESS>')
```

You can also consider the following:

- Recipient address restrictions (ie allowlisting addresses)
- Contract method selectors
- Transaction structure invariants
- Blockhash constraints (on Solana)
</Accordion>

<Accordion title="Can I dynamically add policies per limit order without user friction?">
Yes — this is a common pattern. You can add a policy per order (limit, stop loss, TWAP, etc.) using the DA user, without requiring the end-user to sign for each one.
</Accordion>

<Accordion title="What are common implementation patterns among Turnkey clients using Delegated Access?">
Turnkey's Policy Engine shines through its flexibility. There are many different approaches you can take based on your requirements, but various themes we see include:

- Using **broad policies** with business-controlled API keys
- Using \*\*fine-grained policies, \*\*scoped to predictable transaction shapes
- Using delegated access to implement **limit orders, automation flows, or advanced trading logic** (e.g. perps, TWAPs)
- Ensuring **strong operational security** (e.g. tight scoping & expiring keys) is increasingly common
</Accordion>

### EVM and SVM-Specific Strategies

<Accordion title="Are time-bound transactions supported?">
Yes, on Solana via `solana.tx.recent_blockhash`, which restricts a transaction’s validity to a ~60–90 second window. Not ideal for delayed executions (e.g. limit orders), but useful for immediate, single-use actions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we link to a resource to learn more? something like https://solana.com/developers/guides/advanced/confirmation#transaction-confirmation-tips

</Accordion>

<Accordion title="For EVM transactions, can I enforce token-specific or contract-specific limits?">
Yes, though it’s limited today. You can inspect calldata (e.g., using `eth.tx.data[...]`) and enforce conditions like:

```javascript
eth.tx.to == '<TOKEN_CONTRACT>' &&
eth.tx.data[0..4] == '<ERC20_FUNCTION_SELECTOR>'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will actually have to be [0..10]

```

Granular support for calldata parsing and value limits is coming soon.
</Accordion>

<Accordion title="Is it safe to whitelist routers (e.g. Jupiter) in delegated access policies?">
Not entirely. Even if you allowlist the router, it could still be abused to swap all assets. You can’t control downstream behavior unless you control the contract.

> **Suggestion:** Only allow DA keys to interact with contracts you fully trust or control. Limit scope as much as possible (e.g., to specific instructions, amounts, or recipients).
</Accordion>

<Accordion title="What policy strategies work well for Solana limit orders?">
Use structure-based conditions (instruction count, token recipient), and if possible, include conditions like `recent_blockhash` for ephemeral actions. For limit orders with unknown execution time, dynamic policy creation per order is safer than relying on stale conditions.
</Accordion>
26 changes: 25 additions & 1 deletion embedded-wallets/sub-organization-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,31 @@ Here's an example of a custom HTML email containing an email auth bundle:
![dynamic email auth example](/images/embedded-wallets/img/email-auth-example-dynamic.png)
</Frame>

If you are interested in implementing bespoke, fully-customized email templates, please reach out to [[email protected]](mailto:[email protected]).
### Custom email sender domain

[Enterprise](https://www.turnkey.com/pricing) clients on the **Scale** tier or higher can also customize the email sender domain. To get set up, please reach out to your Turnkey rep to get started but here is what you'll be able to configure:

```js
message InitOtpAuthIntent {
// Optional custom email address from which to send the OTP email
optional string send_from_email_address = "[email protected]";

// Optional custom sender name (e.g. "MyApp Notifications")
optional string send_from_email_sender_name = "MyApp Notifications";

// Optional reply-to email address
optional string reply_to_email_address = "[email protected]";
}
```

Please keep in mind that:

- Email has to be from a pre-whitelisted domain
- If there is no `send_from_email_address` or it's invalid, the other two fields are ignored
- If `send_from_email_sender_name` is absent, it defaults to "Notifications" (again, ONLY if `send_from_email_address` is present and valid)
- If `reply_to_email_address` is absent, then there is no reply-to added. If it is present, it must ALSO be from a valid, whitelisted domain, but it doesn't have to be the same email address as the `send_from_email_address` one (though once again, this first one MUST be present, or the other two feature are ignored)

If you are interested in implementing bespoke, fully-customized email templates and sender domain, please reach out to [[email protected]](mailto:[email protected]).

### Credential validity checks

Expand Down
6 changes: 4 additions & 2 deletions reference/aa-wallets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ Visit [the ZeroDev documentation](https://docs.zerodev.app/sdk/signers/turnkey)

Create a Biconomy Smart Account and add a Turnkey signer to manage your private key and authentication methods by using Turnkey's API.

For detailed code snippets and an integration guide, refer to the [Biconomy documentation](https://docs.biconomy.io/account/signers/turnkey).
For detailed code snippets and an integration guide, refer to the [Biconomy documentation](https://docs.biconomy.io/tutorials/signers/turnkey/).

Copy link
Contributor

@andrewkmin andrewkmin May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we pick one or the other? or maybe tweak the organization a little bit?

Create a Biconomy Smart Account and add a Turnkey signer to manage your private key and authentication methods by using Turnkey's API.

Ethereum's latest EIP-7702 standard gives superpowers to Externally Owned Accounts (EOAs). Biconomy has a guide on how to leverage gas abstracted transactions with Turnkey and Biconomy, enabling Turnkey EOAs to become smart accounts through delegation to Nexus. The tutorial showcases gas abstracted batch execution - users can pay gas fees with ERC20 tokens from their EOA. Refer to [Biconomy documentation](https://docs.biconomy.io/smarteoa/demos/turnkey-7702-gasless/) to get started.

For a more generalized integration guide, refer to the [Biconomy <> Turnkey tutorial](https://docs.biconomy.io/tutorials/signers/turnkey/).

Ethereum's latest EIP-7702 standard gives superpowers to Externally Owned Accounts (EOAs). Biconomy has a guide on how to leverage gas abstracted transactions with Turnkey and Biconomy, enabling Turnkey EOAs to become smart accounts through delegation to Nexus. The tutorial showcases gas abstracted batch execution - users can pay gas fees with ERC20 tokens from their EOA. Refer to [Biconomy documentation](https://docs.biconomy.io/smarteoa/demos/turnkey-7702-gasless/) to get started.

## permissionless.js Accounts

permissionless.js is a TypeScript library built on viem for building with ERC-4337 smart accounts, bundlers, paymasters, and user operations.

permissionless.js defines the `SmartAccountSigner` interface which supports Turnkey as a signer. You can find a detailed example for integrating a Turnkey signer with permissionless.js in the [Pimlico documentation](https://docs.pimlico.io/permissionless/how-to/signers/turnkey).
permissionless.js defines the `SmartAccountSigner` interface which supports Turnkey as a signer. You can find a detailed example for integrating a Turnkey signer with permissionless.js in the [Pimlico documentation](https://docs.pimlico.io/permissionless/how-to/signers/turnkey).
2 changes: 1 addition & 1 deletion reference/solana-gasless-transactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Here’s how to take advantage of Turnkey’s composable primitives for gas abst

### **Option 1: Using Solana Address Fee Payer**

For this approach, we will leverage [examples/with-solana](https://github.com/tkhq/sdk/tree/main/examples/with-solana), a full example of Solana transaction construction, and broadcasting using [@turnkey/with-solana](https://www.npmjs.com/package/@turnkey/solana). For a more comprehensive, full-stack example that leverages passkeys, check out [<u>examples/with-solana-passkeys</u>](https://github.com/tkhq/sdk/tree/main/examples/with-solana-passkeys).
For this approach, we will leverage [examples/with-solana](https://github.com/tkhq/sdk/tree/main/examples/with-solana), a full example demonstrating how to construct a Solana transaction, sign using [@turnkey/solana](https://www.npmjs.com/package/@turnkey/solana), and broadcast via public node. For a more comprehensive, full-stack example that leverages passkeys, check out [<u>examples/with-solana-passkeys</u>](https://github.com/tkhq/sdk/tree/main/examples/with-solana-passkeys).

\
First, you’ll need to use one of the two optional environmental variables: `SOLANA_ADDRESS_FEE_PAYER`. While optional, the variable is necessary to create a separate fee payer address. The setup is as simple as passing in the fee payer address into the [<u>withFeePayer.ts</u>](https://github.com/tkhq/sdk/blob/main/examples/with-solana/src/withFeePayer.ts)
Expand Down