Skip to content

Commit d9300de

Browse files
authored
Merge pull request #101 from ten-protocol/ziga/docs_gw_sessionKeys_testnet
2 parents 37c95b2 + 611fcfa commit d9300de

3 files changed

Lines changed: 357 additions & 158 deletions

File tree

docs/4-write-ten-dapp/4-session-keys.md

Lines changed: 163 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,148 +2,207 @@
22
sidebar_position: 4
33
---
44

5-
# Account Abstraction
5+
# Account Abstraction & Session Keys in TEN
66

7-
The key feature of [Account Abstraction](https://medium.com/p/2e85bde4c54d) (EIP-4337) is “session keys” (SKs) through a proxy smart contract.
8-
SKs allow users to interact with the blockchain without signing every transaction, which is a major UX improvement.
7+
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.
8+
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.
99

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

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

14-
## Solution overview
16+
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.
1517

16-
Imagine you're developing an on-chain game, and you want a smooth UX without the distraction of signing every move.
18+
---
19+
20+
## High‑level flow for dApps
1721

18-
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.
22+
Imagine an on‑chain game that wants a smooth UX without prompting the user to sign every move:
1923

20-
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.
21-
With TEN, the dApp developer doesn't have to worry about this, because the SKs are managed by TEEs.
24+
1. **User authenticates to the gateway** and gets an `encryptionToken`.
25+
2. **Gateway creates a session key** for this user and exposes its address.
26+
3. **User funds the session key** with a normal, user‑signed transfer.
27+
4. **Game sends transactions using the session key** (no additional wallet popups).
28+
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.
2229

23-
## Usage
30+
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.
2431

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

28-
### When the game starts
34+
## Using session keys via custom queries
2935

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

33-
- 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.
34-
- 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.
35-
- Ask the user to sign this transaction with their standard wallet, and submit it to the network using the library of your choice.
36-
- The session key is automatically activated and ready to use.
38+
All examples below assume you are calling through the gateway:
3739

38-
### During the game
40+
```text
41+
https://testnet-rpc.ten.xyz/v1/?token=<EncryptionToken>
42+
```
3943

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

43-
```json
44-
{
45-
"sessionKeyAddress": "0x...", // The session key address
46-
"tx": "base64_encoded_transaction" // The unsigned transaction encoded as base64
46+
To create a new session key for the authenticated user:
47+
48+
- Call `eth_getStorageAt` with:
49+
- **address**: `0x0000000000000000000000000000000000000003`
50+
- other parameters can be left as in the example below.
51+
52+
The response returns the **hex‑encoded address** of the new session key.
53+
54+
```javascript
55+
async function createSessionKey(encryptionToken) {
56+
const response = await fetch(
57+
`https://testnet-rpc.ten.xyz/v1/?token=${encryptionToken}`,
58+
{
59+
method: "POST",
60+
headers: { "Content-Type": "application/json" },
61+
body: JSON.stringify({
62+
jsonrpc: "2.0",
63+
method: "eth_getStorageAt",
64+
params: [
65+
"0x0000000000000000000000000000000000000003", // create SK
66+
"0x0",
67+
"latest",
68+
],
69+
id: 1,
70+
}),
71+
},
72+
);
73+
74+
const data = await response.json();
75+
return data.result; // Session key address (0x...)
4776
}
4877
```
4978

50-
The platform will sign the transactions on behalf of the user.
79+
### 2. Fund the session key
5180

52-
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.
53-
If the SK runs out of balance, you must ask the user to move more funds to the SK.
81+
Once you have the session key address, fund it with a **normal user‑signed transaction** from the user’s main wallet:
5482

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

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

59-
- `eth_getStorageAt` with address `0x0000000000000000000000000000000000000004` — permanently removes the session key. This requires the following parameters:
88+
---
89+
90+
## Sending transactions with a session key
91+
92+
There are two main integration patterns:
93+
94+
1. **Custom‑query based signing** (low‑level; uses `eth_getStorageAt`).
95+
2. **Direct `eth_sendTransaction` from the session key** (simpler, when your client library supports it).
96+
97+
### 1) Custom‑query signing (low‑level)
98+
99+
You can ask the gateway to sign and submit an unsigned transaction using the session key by calling:
100+
101+
- `eth_getStorageAt` with **address** `0x0000000000000000000000000000000000000005`
102+
- The second parameter encodes:
60103

61104
```json
62105
{
63-
"sessionKeyAddress": "0x..." // The session key address to delete
106+
"sessionKeyAddress": "0x...", // Session key address
107+
"tx": "base64_encoded_transaction" // Unsigned transaction, base64‑encoded
64108
}
65109
```
66110

67-
### Finishing the game
111+
Example:
68112

69-
When a game ends, you must move the remaining funds back to the main address.
113+
```javascript
114+
async function sendWithSessionKey(
115+
encryptionToken,
116+
sessionKeyAddress,
117+
unsignedTx,
118+
) {
119+
const txBase64 = btoa(JSON.stringify(unsignedTx)); // client‑side encoding
120+
121+
const response = await fetch(
122+
`https://testnet-rpc.ten.xyz/v1/?token=${encryptionToken}`,
123+
{
124+
method: "POST",
125+
headers: { "Content-Type": "application/json" },
126+
body: JSON.stringify({
127+
jsonrpc: "2.0",
128+
method: "eth_getStorageAt",
129+
params: [
130+
"0x0000000000000000000000000000000000000005", // sign+send with SK
131+
JSON.stringify({
132+
sessionKeyAddress,
133+
tx: txBase64,
134+
}),
135+
"latest",
136+
],
137+
id: 1,
138+
}),
139+
},
140+
);
70141

71-
- 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.
142+
const data = await response.json();
143+
return data.result; // tx hash
144+
}
145+
```
72146

73-
## Example implementation
147+
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.
74148

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

77-
```javascript
78-
// 1. Create a session key
79-
async function createSessionKey() {
80-
const response = await fetch("https://testnet.ten.xyz/v1/", {
81-
method: "POST",
82-
headers: { "Content-Type": "application/json" },
83-
body: JSON.stringify({
84-
jsonrpc: "2.0",
85-
method: "eth_getStorageAt",
86-
params: ["0x0000000000000000000000000000000000000003", "0x0", "latest"],
87-
id: 1,
88-
}),
89-
});
151+
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.
90152

91-
const data = await response.json();
92-
return data.result; // Returns the session key address
93-
}
153+
**Requirements:**
94154

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

101-
// 3. Send unsigned transactions using the session key
102-
async function sendUnsignedTransaction(sessionKeyAddress, unsignedTx) {
103-
const txBase64 = btoa(JSON.stringify(unsignedTx)); // Convert to base64
104-
105-
const response = await fetch("https://testnet.ten.xyz/v1/", {
106-
method: "POST",
107-
headers: { "Content-Type": "application/json" },
108-
body: JSON.stringify({
109-
jsonrpc: "2.0",
110-
method: "eth_getStorageAt",
111-
params: [
112-
"0x0000000000000000000000000000000000000005",
113-
JSON.stringify({
114-
sessionKeyAddress: sessionKeyAddress,
115-
tx: txBase64,
116-
}),
117-
"latest",
118-
],
119-
id: 1,
120-
}),
121-
});
159+
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.
122160

123-
const data = await response.json();
124-
return data.result; // Returns the transaction hash
125-
}
161+
**Error handling:**
126162

127-
// 4. Delete the session key when done
128-
async function deleteSessionKey(sessionKeyAddress) {
129-
const response = await fetch("https://testnet.ten.xyz/v1/", {
130-
method: "POST",
131-
headers: { "Content-Type": "application/json" },
132-
body: JSON.stringify({
133-
jsonrpc: "2.0",
134-
method: "eth_getStorageAt",
135-
params: [
136-
"0x0000000000000000000000000000000000000004",
137-
JSON.stringify({
138-
sessionKeyAddress: sessionKeyAddress,
139-
}),
140-
"latest",
141-
],
142-
id: 1,
143-
}),
144-
});
163+
- 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.
164+
- If the session key lacks sufficient balance, the transaction is rejected by the network (not by the gateway).
165+
- Standard authentication errors apply if the `encryptionToken` is missing or invalid.
145166

146-
const data = await response.json();
147-
return data.result; // Returns 0x01 for success, 0x00 for failure
167+
---
168+
169+
## Deleting and expiring session keys
170+
171+
### Manual deletion via custom query
172+
173+
To explicitly delete a session key:
174+
175+
- Call `eth_getStorageAt` with **address** `0x0000000000000000000000000000000000000004` and a JSON payload:
176+
177+
```json
178+
{
179+
"sessionKeyAddress": "0x..." // Session key address to delete
148180
}
149181
```
182+
183+
The gateway:
184+
185+
- Removes the session key from storage (access to the private key is lost forever).
186+
- **Sweeps remaining funds** back to the user’s primary account, using its internal `TxSender`.
187+
188+
### Automatic expiration & fund recovery
189+
190+
In addition, the gateway runs a background **SessionKeyExpirationService**:
191+
192+
- Tracks last activity for each session key.
193+
- Periodically scans for keys older than `--sessionKeyExpirationThreshold`.
194+
- For each candidate, it:
195+
- Reloads the owning user.
196+
- Attempts to move remaining funds back to the user’s primary account.
197+
- Removes the key from the activity tracker and persists updated state.
198+
199+
---
200+
201+
## UX & safety considerations for dApp developers
202+
203+
- **Balance management**: Track the session key’s balance (e.g. via `eth_getBalance`) and top it up when needed.
204+
- **Per‑dApp keys**: Use separate session keys per dApp / game instance if you want stronger isolation.
205+
- **Lifecycle**: Delete or let the gateway expire session keys when they are no longer needed to keep the system tidy.
206+
- **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.
207+
208+
Combined with **programmable access tokens** (`encryptionToken`) and the TEN gateway, session keys give you EIP‑4337‑style UX without additional contracts or bundler infrastructure.

docs/4-write-ten-dapp/6-testnet.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,33 @@ sidebar_position: 6
44

55
# Testnet
66

7-
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.
7+
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.
88

99
## TEN Gateway
1010

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

1313
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.
1414

15-
## TEN Sepolia Faucet
15+
## Getting funds on the testnet
1616

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

19-
Using the steps provided, you can request testnet ETH from the faucet available on the TEN Gas Station.
20-
21-
## **Requesting Testnet ETH**
2219
1. Make a note of your EVM wallet address or copy it to your clipboard.
23-
2. Head over to [TEN Gas Station](https://testnet-faucet.ten.xyz/).
20+
2. Head over to [TEN Gas Station](https://faucet.ten.xyz/).
2421
3. Paste your EVM wallet address into the wallet address field.
2522
4. Log in with your Discord and X (Twitter) accounts.
26-
5. Then, complete the available tasks.
23+
5. Complete the available tasks to receive testnet ETH.
2724

25+
### **Option 2: Bridging Sepolia testnet funds to TEN**
2826

29-
## TENscan
27+
If you already have ETH on the Sepolia testnet, you can bridge it directly to TEN Sepolia:
3028

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

34+
## TENScan
35+
36+
You can use the TEN block explorer to view transaction data occurring on the testnet [here](https://testnet.tenscan.io/).

0 commit comments

Comments
 (0)