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
123 changes: 123 additions & 0 deletions src/pages/sdk/go/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,136 @@ for _, resp := range responses {
}
```

## Account Keychain

The `keychain` package provides typed helpers for Tempo's [Account Keychain precompile](/protocol/transactions/AccountKeychain), enabling access key management and signing directly from Go.

:::info
Enhanced access key features — periodic spending limits and call scoping — require the [T3 network upgrade](/protocol/upgrades/t3).
:::

```go [keychain_manage.go]
package main

import (
"context"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/tempoxyz/tempo-go/pkg/client"
"github.com/tempoxyz/tempo-go/pkg/keychain"
"github.com/tempoxyz/tempo-go/pkg/signer"
"github.com/tempoxyz/tempo-go/pkg/transaction"
)

func main() {
c := client.New("https://rpc.tempo.xyz")
s, _ := signer.NewSigner("0xYOUR_PRIVATE_KEY")

ctx := context.Background()
nonce, _ := c.GetTransactionCount(ctx, s.Address().Hex())

accessKeyAddr := common.HexToAddress("<ACCESS_KEY_ADDRESS>")

// Authorize a new access key (secp256k1, no expiry):
restrictions := keychain.NewKeyRestrictions(0)
call, _ := keychain.AuthorizeKey(accessKeyAddr, keychain.SignatureTypeSecp256k1, restrictions)

tx := transaction.NewBuilder(big.NewInt(4217)).
SetNonce(nonce).
SetGas(200000).
SetMaxFeePerGas(big.NewInt(20000000000)).
SetMaxPriorityFeePerGas(big.NewInt(1000000000)).
AddCall(call.To, big.NewInt(0), call.Data).
Build()

// Authorize with a spending limit:
token := common.HexToAddress("<TOKEN_ADDRESS>")
restrictions = keychain.NewKeyRestrictions(0).
WithLimits([]keychain.TokenLimit{{Token: token, Amount: big.NewInt(1_000_000)}})
call, _ = keychain.AuthorizeKey(accessKeyAddr, keychain.SignatureTypeSecp256k1, restrictions)

// Authorize with call scopes (restrict to specific contracts/functions):
scope := keychain.NewCallScopeBuilder(token).
Transfer(nil).
Approve(nil).
Build()
restrictions = keychain.NewKeyRestrictions(0).
WithAllowedCalls([]keychain.CallScope{scope})
call, _ = keychain.AuthorizeKey(accessKeyAddr, keychain.SignatureTypeSecp256k1, restrictions)

// Full example: 24h expiry + spending limit + call scope:
expiry := uint64(time.Now().Add(24 * time.Hour).Unix())
restrictions = keychain.NewKeyRestrictions(expiry).
WithLimits([]keychain.TokenLimit{{Token: token, Amount: big.NewInt(1_000_000)}}).
WithAllowedCalls([]keychain.CallScope{
keychain.NewCallScopeBuilder(token).Transfer(nil).Build(),
})
call, _ = keychain.AuthorizeKey(accessKeyAddr, keychain.SignatureTypeSecp256k1, restrictions)

// Revoke an access key (permanent, cannot be re-authorized):
call, _ = keychain.RevokeKey(accessKeyAddr)

// Update spending limit for a key-token pair:
call, _ = keychain.UpdateSpendingLimit(accessKeyAddr, token, big.NewInt(2_000_000))

// Replace all call scopes for a key:
call, _ = keychain.SetAllowedCalls(accessKeyAddr, []keychain.CallScope{
keychain.NewCallScopeBuilder(token).Transfer(nil).Build(),
})

// Remove a target contract from allowed call list:
call, _ = keychain.RemoveAllowedCalls(accessKeyAddr, token)

_ = ctx
_ = tx
_ = call
}
```

### Signing with an Access Key

Use `keychain.SignWithAccessKey` to sign a transaction as an access key holder:

```go [access_key_sign.go]
accessKeySigner, _ := signer.NewSigner("<ACCESS_KEY_PRIVATE_KEY>")
rootAccount := common.HexToAddress("<ROOT_ACCOUNT_ADDRESS>")

tx := transaction.NewBuilder(big.NewInt(4217)).
SetNonce(nonce).
SetGas(100000).
SetMaxFeePerGas(big.NewInt(20000000000)).
SetMaxPriorityFeePerGas(big.NewInt(1000000000)).
AddCall(recipient, big.NewInt(0), data).
Build()

keychain.SignWithAccessKey(tx, accessKeySigner, rootAccount) // [!code hl]
serialized, _ := transaction.Serialize(tx, nil)
hash, _ := c.SendRawTransaction(ctx, serialized)
```

### Query Remaining Spending Limit

```go [query_limit.go]
calldata := keychain.EncodeGetRemainingLimitCalldata(
common.HexToAddress("<ACCOUNT_ADDRESS>"),
common.HexToAddress("<ACCESS_KEY_ADDRESS>"),
common.HexToAddress("<TOKEN_ADDRESS>"),
)
result, _ := c.Call(ctx, keychain.GetKeychainAddress().Hex(), calldata)
remaining := keychain.ParseRemainingLimitResult(result)
fmt.Printf("Remaining: %s\n", remaining.String())
```

## Packages

| Package | Description |
| --- | --- |
| `transaction` | TempoTransaction encoding, signing, and validation |
| `client` | RPC client for interacting with Tempo nodes |
| `signer` | Key management and signature generation |
| `keychain` | Account Keychain precompile: access key management and signing |

## Next Steps

Expand Down
136 changes: 136 additions & 0 deletions src/pages/sdk/python/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,142 @@ tx = TempoTransaction.create(
)
```

## Account Keychain

The `AccountKeychain` class provides typed helpers for Tempo's [Account Keychain precompile](/protocol/transactions/AccountKeychain), enabling access key management directly from Python.

:::info
Enhanced access key features — periodic spending limits and call scoping — require the [T3 network upgrade](/protocol/upgrades/t3).
:::

```python [keychain.py]
from pytempo import (
TempoTransaction, Call,
KeyRestrictions, SignatureType, TokenLimit, CallScope,
)
from pytempo.contracts import AccountKeychain, ALPHA_USD
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz"))

# Authorize a new access key (secp256k1, no expiry):
call = AccountKeychain.authorize_key(
key_id="<ACCESS_KEY_ADDRESS>",
signature_type=SignatureType.SECP256K1,
restrictions=KeyRestrictions(expiry=0),
)
tx = TempoTransaction.create(
chain_id=w3.eth.chain_id,
gas_limit=200_000,
max_fee_per_gas=w3.eth.gas_price * 2,
max_priority_fee_per_gas=w3.eth.gas_price,
nonce=w3.eth.get_transaction_count(account.address),
calls=(call,),
)

# Authorize with a spending limit:
call = AccountKeychain.authorize_key(
key_id="<ACCESS_KEY_ADDRESS>",
signature_type=SignatureType.SECP256K1,
restrictions=KeyRestrictions(
expiry=0,
limits=[TokenLimit(token=ALPHA_USD, limit=1_000_000)],
),
)

# Authorize with call scopes (restrict to specific contracts/functions):
call = AccountKeychain.authorize_key(
key_id="<ACCESS_KEY_ADDRESS>",
signature_type=SignatureType.SECP256K1,
restrictions=KeyRestrictions(
expiry=0,
allowed_calls=[
CallScope.transfer(target=ALPHA_USD),
CallScope.approve(target=ALPHA_USD),
],
),
)

# Full example: 24h expiry + spending limit + call scope:
import time
expiry = int(time.time()) + 86400
call = AccountKeychain.authorize_key(
key_id="<ACCESS_KEY_ADDRESS>",
signature_type=SignatureType.SECP256K1,
restrictions=KeyRestrictions(
expiry=expiry,
limits=[TokenLimit(token=ALPHA_USD, limit=1_000_000)],
allowed_calls=[CallScope.transfer(target=ALPHA_USD)],
),
)

# Revoke an access key (permanent, cannot be re-authorized):
call = AccountKeychain.revoke_key(key_id="<ACCESS_KEY_ADDRESS>")

# Update spending limit for a key-token pair:
call = AccountKeychain.update_spending_limit(
key_id="<ACCESS_KEY_ADDRESS>",
token=str(ALPHA_USD),
new_limit=2_000_000,
)

# Replace all call scopes for a key:
call = AccountKeychain.set_allowed_calls(
key_id="<ACCESS_KEY_ADDRESS>",
scopes=[
CallScope.transfer(target=ALPHA_USD),
CallScope.unrestricted(target="<CONTRACT_ADDRESS>"),
],
)

# Remove a target contract from allowed call list:
call = AccountKeychain.remove_allowed_calls(
key_id="<ACCESS_KEY_ADDRESS>",
target="<TARGET_ADDRESS>",
)

# Query key info (read-only):
key_info = AccountKeychain.get_key(
w3,
account_address="<ACCOUNT_ADDRESS>",
key_id="<ACCESS_KEY_ADDRESS>",
)
print(key_info)
# {'signature_type': 0, 'key_id': '0x...', 'expiry': 1893456000, ...}

# Query remaining spending limit:
remaining = AccountKeychain.get_remaining_limit(
w3,
account_address="<ACCOUNT_ADDRESS>",
key_id="<ACCESS_KEY_ADDRESS>",
token_address=str(ALPHA_USD),
)
print(f"Remaining: {remaining}")
```

### Signing with an Access Key

Use `sign_access_key` to sign a transaction as an access key holder:

```python [access_key_sign.py]
from pytempo import TempoTransaction, Call

tx = TempoTransaction.create(
chain_id=w3.eth.chain_id,
gas_limit=100_000,
max_fee_per_gas=w3.eth.gas_price * 2,
max_priority_fee_per_gas=w3.eth.gas_price,
nonce=w3.eth.get_transaction_count(root_account_address),
calls=(Call.create(to="<CONTRACT_ADDRESS>"),),
)

signed_tx = tx.sign_access_key( # [!code hl]
access_key_private_key="<ACCESS_KEY_PRIVATE_KEY>", # [!code hl]
root_account="<ROOT_ACCOUNT_ADDRESS>", # [!code hl]
) # [!code hl]
tx_hash = w3.eth.send_raw_transaction(signed_tx.encode())
```

## Next Steps

After setting up the Python SDK, you can:
Expand Down
Loading