Skip to content
Merged
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
267 changes: 163 additions & 104 deletions docs/4-write-ten-dapp/4-session-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,148 +2,207 @@
sidebar_position: 4
---

# Account Abstraction
# Account Abstraction & Session Keys in TEN

The key feature of [Account Abstraction](https://medium.com/p/2e85bde4c54d) (EIP-4337) is “session keys” (SKs) through a proxy smart contract.
SKs allow users to interact with the blockchain without signing every transaction, which is a major UX improvement.
In the classic Account Abstraction model (EIP‑4337) described on [ethereum.org](https://ethereum.org/roadmap/account-abstraction/), “session keys” (SKs) are typically implemented using proxy smart contracts and a bundler.
TEN’s Account Abstraction design, outlined in [this post](https://medium.com/p/2e85bde4c54d), instead provides **native session keys managed by the gateway and TEEs**, eliminating the need for proxy contracts or a bundler.

TEN supports "native" SKs - these are managed by the platform and do not require a proxy contract.
Session keys in TEN are:

In TEN, SKs are managed by dApp developers through dedicated RPC endpoints.
- **Ephemeral Ethereum accounts** managed by the gateway on behalf of a user.
- **Linked to the user’s viewing key / encryption token** ([details](https://docs.ten.xyz/docs/write-ten-dapp/programmable-gateway-access)).
- **Used to sign and submit transactions** via the gateway, enabling “no‑click” UX for dApps.

## Solution overview
Each user can have **multiple session keys** (up to 100), and the gateway can automatically expire idle keys and recover funds back to the user’s primary account.

Imagine you're developing an on-chain game, and you want a smooth UX without the distraction of signing every move.
---

## High‑level flow for dApps

Conceptually, the game will create a session key (SK) for the user, then ask the user to move some funds to that address, and then create “move” transactions signed with the SK.
Imagine an on‑chain game that wants a smooth UX without prompting the user to sign every move:

If the game were to create the SK in the browser, there would be a risk of the user losing the SK, and the funds associated with it, in case of an accidental exit.
With TEN, the dApp developer doesn't have to worry about this, because the SKs are managed by TEEs.
1. **User authenticates to the gateway** and gets an `encryptionToken`.
2. **Gateway creates a session key** for this user and exposes its address.
3. **User funds the session key** with a normal, user‑signed transfer.
4. **Game sends transactions using the session key** (no additional wallet popups).
5. **Gateway tracks activity and can expire/clean up idle session keys**, sweeping funds back to the account which was first authenticated with the gateway.

## Usage
From the dApp’s point of view, a session key behaves like a normal Ethereum account whose private key is held inside the TEE‑backed gateway.

The steps below describe the implementation for a game developer—the primary use case for SKs.
Note that SKs can be used for any dApp that requires a no‑click UX.
---

### When the game starts
## Using session keys via custom queries

Before the user can start playing, the game must create the SK and ask the user to move some funds to that address.
The funds will be used to pay for moves.
On TEN, session key management is exposed as **custom queries** behind standard JSON‑RPC, primarily via `eth_getStorageAt` with special “method addresses”.

- Call the RPC `eth_getStorageAt` with address `0x0000000000000000000000000000000000000003` - this will return the hex-encoded address of the SK. The dApp needs to store this address for future use.
- Create a normal transaction that transfers some ETH to the SK. The amount depends on how many "moves" the user is prepared to prepay for.
- Ask the user to sign this transaction with their standard wallet, and submit it to the network using the library of your choice.
- The session key is automatically activated and ready to use.
All examples below assume you are calling through the gateway:

### During the game
```text
https://testnet-rpc.ten.xyz/v1/?token=<EncryptionToken>
```

After sending funds to the SK, create a transaction for each move, but don't ask the user to sign them.
Instead, submit them to the network unsigned using the RPC `eth_getStorageAt` with address `0x0000000000000000000000000000000000000005` and the following parameters:
### 1. Create a session key

```json
{
"sessionKeyAddress": "0x...", // The session key address
"tx": "base64_encoded_transaction" // The unsigned transaction encoded as base64
To create a new session key for the authenticated user:

- Call `eth_getStorageAt` with:
- **address**: `0x0000000000000000000000000000000000000003`
- other parameters can be left as in the example below.

The response returns the **hex‑encoded address** of the new session key.

```javascript
async function createSessionKey(encryptionToken) {
const response = await fetch(
`https://testnet-rpc.ten.xyz/v1/?token=${encryptionToken}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: [
"0x0000000000000000000000000000000000000003", // create SK
"0x0",
"latest",
],
id: 1,
}),
},
);

const data = await response.json();
return data.result; // Session key address (0x...)
}
```

The platform will sign the transactions on behalf of the user.
### 2. Fund the session key

As a game developer, you are responsible for keeping track of the SK’s balance. You can also query the network for the balance of the SK address.
If the SK runs out of balance, you must ask the user to move more funds to the SK.
Once you have the session key address, fund it with a **normal user‑signed transaction** from the user’s main wallet:

### Managing session keys
- Build a standard `eth_sendTransaction` / wallet transaction from the user’s account to the session key address.
- Let the wallet sign and submit it.

TEN provides additional RPC endpoints for managing session keys:
The gateway observes this and considers the session key funded and ready to use.

- `eth_getStorageAt` with address `0x0000000000000000000000000000000000000004` — permanently removes the session key. This requires the following parameters:
---

## Sending transactions with a session key

There are two main integration patterns:

1. **Custom‑query based signing** (low‑level; uses `eth_getStorageAt`).
2. **Direct `eth_sendTransaction` from the session key** (simpler, when your client library supports it).

### 1) Custom‑query signing (low‑level)

You can ask the gateway to sign and submit an unsigned transaction using the session key by calling:

- `eth_getStorageAt` with **address** `0x0000000000000000000000000000000000000005`
- The second parameter encodes:

```json
{
"sessionKeyAddress": "0x..." // The session key address to delete
"sessionKeyAddress": "0x...", // Session key address
"tx": "base64_encoded_transaction" // Unsigned transaction, base64‑encoded
}
```

### Finishing the game
Example:

When a game ends, you must move the remaining funds back to the main address.
```javascript
async function sendWithSessionKey(
encryptionToken,
sessionKeyAddress,
unsignedTx,
) {
const txBase64 = btoa(JSON.stringify(unsignedTx)); // client‑side encoding

const response = await fetch(
`https://testnet-rpc.ten.xyz/v1/?token=${encryptionToken}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: [
"0x0000000000000000000000000000000000000005", // sign+send with SK
JSON.stringify({
sessionKeyAddress,
tx: txBase64,
}),
"latest",
],
id: 1,
}),
},
);

- Create a transaction (tx) that moves the funds back from the SK to the main address. Submit it unsigned, because the funds are controlled by the SK.
const data = await response.json();
return data.result; // tx hash
}
```

## Example implementation
The gateway first confirms that the session key belongs to the user identified by `encryptionToken`. It then signs the transaction with the session key’s private key and sends it to the TEN node.

Here's a complete example of how to implement session keys in a JavaScript dApp:
### 2) Direct `eth_sendTransaction` with a session key

```javascript
// 1. Create a session key
async function createSessionKey() {
const response = await fetch("https://testnet.ten.xyz/v1/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: ["0x0000000000000000000000000000000000000003", "0x0", "latest"],
id: 1,
}),
});
You can send transactions directly using `eth_sendTransaction` by setting the `from` field to a session key address that belongs to the authenticated user. The gateway signs the transaction with the session key's private key and broadcasts it.

const data = await response.json();
return data.result; // Returns the session key address
}
**Requirements:**

// 2. Fund the session key (user signs this transaction)
async function fundSessionKey(sessionKeyAddress, amount) {
// This would be a normal transaction signed by the user's wallet
// transferring ETH to the session key address
}
- The request must include the user's `encryptionToken` (same authentication as other gateway calls).
- The `from` address must be a session key owned by that user.
- The transaction must include all required fields (`gas`, `gasPrice` or `maxFeePerGas`/`maxPriorityFeePerGas`, `nonce`, etc.). The gateway does not auto-fill missing fields.

// 3. Send unsigned transactions using the session key
async function sendUnsignedTransaction(sessionKeyAddress, unsignedTx) {
const txBase64 = btoa(JSON.stringify(unsignedTx)); // Convert to base64

const response = await fetch("https://testnet.ten.xyz/v1/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: [
"0x0000000000000000000000000000000000000005",
JSON.stringify({
sessionKeyAddress: sessionKeyAddress,
tx: txBase64,
}),
"latest",
],
id: 1,
}),
});
The gateway verifies that the session key belongs to the user identified by `encryptionToken`, signs the transaction with the session key's private key, and sends it to the TEN node.

const data = await response.json();
return data.result; // Returns the transaction hash
}
**Error handling:**

// 4. Delete the session key when done
async function deleteSessionKey(sessionKeyAddress) {
const response = await fetch("https://testnet.ten.xyz/v1/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getStorageAt",
params: [
"0x0000000000000000000000000000000000000004",
JSON.stringify({
sessionKeyAddress: sessionKeyAddress,
}),
"latest",
],
id: 1,
}),
});
- If `from` is not a session key for the authenticated user, the call fails with an error indicating the session key address was not found.
- If the session key lacks sufficient balance, the transaction is rejected by the network (not by the gateway).
- Standard authentication errors apply if the `encryptionToken` is missing or invalid.

const data = await response.json();
return data.result; // Returns 0x01 for success, 0x00 for failure
---

## Deleting and expiring session keys

### Manual deletion via custom query

To explicitly delete a session key:

- Call `eth_getStorageAt` with **address** `0x0000000000000000000000000000000000000004` and a JSON payload:

```json
{
"sessionKeyAddress": "0x..." // Session key address to delete
}
```

The gateway:

- Removes the session key from storage (access to the private key is lost forever).
- **Sweeps remaining funds** back to the user’s primary account, using its internal `TxSender`.

### Automatic expiration & fund recovery

In addition, the gateway runs a background **SessionKeyExpirationService**:

- Tracks last activity for each session key.
- Periodically scans for keys older than `--sessionKeyExpirationThreshold`.
- For each candidate, it:
- Reloads the owning user.
- Attempts to move remaining funds back to the user’s primary account.
- Removes the key from the activity tracker and persists updated state.

---

## UX & safety considerations for dApp developers

- **Balance management**: Track the session key’s balance (e.g. via `eth_getBalance`) and top it up when needed.
- **Per‑dApp keys**: Use separate session keys per dApp / game instance if you want stronger isolation.
- **Lifecycle**: Delete or let the gateway expire session keys when they are no longer needed to keep the system tidy.
- **Security**: The session key’s private key never leaves the TEE‑backed gateway, but users should still treat them as scoped spending keys and only fund them with limited amounts.

Combined with **programmable access tokens** (`encryptionToken`) and the TEN gateway, session keys give you EIP‑4337‑style UX without additional contracts or bundler infrastructure.
24 changes: 14 additions & 10 deletions docs/4-write-ten-dapp/6-testnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,33 @@ sidebar_position: 6

# Testnet

TEN Sepolia is our testnet that replicates the capabilities of the TEN Mainnet network. Linked to the Sepolia testnet, you can authenticate with the testnet TEN gateway, use the TEN faucet, and develop and deploy dApps for testing.
TEN Sepolia is our testnet environment that mirrors the capabilities of the TEN Mainnet. In this environment, you can authenticate with the TEN testnet gateway, use the TEN faucet, and develop and deploy dApps for testing.

## TEN Gateway

![TEN Hosted Gateway](../assets/gateway.png)

Visit the TEN testnet gateway [here](https://testnet.ten.xyz/). Follow the on‑screen instructions to authenticate with the hosted gateway that allows you to interact with the testnet.

## TEN Sepolia Faucet
## Getting funds on the testnet

![TEN Discord Faucet](../assets/faucet.ten.xyz.jpg)
### **Option 1: Requesting Testnet ETH from the TEN Gas Station**

Using the steps provided, you can request testnet ETH from the faucet available on the TEN Gas Station.

## **Requesting Testnet ETH**
1. Make a note of your EVM wallet address or copy it to your clipboard.
2. Head over to [TEN Gas Station](https://testnet-faucet.ten.xyz/).
2. Head over to [TEN Gas Station](https://faucet.ten.xyz/).
3. Paste your EVM wallet address into the wallet address field.
4. Log in with your Discord and X (Twitter) accounts.
5. Then, complete the available tasks.
5. Complete the available tasks to receive testnet ETH.

### **Option 2: Bridging Sepolia testnet funds to TEN**

## TENscan
If you already have ETH on the Sepolia testnet, you can bridge it directly to TEN Sepolia:

You can use the TEN block explorer to view transaction data occurring on the testnet [here](https://testnet.tenscan.io/).
1. Go to the TEN testnet bridge: [`https://testnet-bridge.ten.xyz/`](https://testnet-bridge.ten.xyz/).
2. Connect your wallet (Sepolia network selected).
3. Choose how much Sepolia ETH you want to bridge from L1 to L2 (TEN).
4. Confirm the transaction in your wallet and wait for it to finalize.

## TENScan

You can use the TEN block explorer to view transaction data occurring on the testnet [here](https://testnet.tenscan.io/).
Loading