diff --git a/src/pages/sdk/go/index.mdx b/src/pages/sdk/go/index.mdx index c98fdf77..0d64006a 100644 --- a/src/pages/sdk/go/index.mdx +++ b/src/pages/sdk/go/index.mdx @@ -285,6 +285,128 @@ 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("") + + // 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("") + 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("") +rootAccount := common.HexToAddress("") + +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(""), + common.HexToAddress(""), + common.HexToAddress(""), +) +result, _ := c.Call(ctx, keychain.GetKeychainAddress().Hex(), calldata) +remaining := keychain.ParseRemainingLimitResult(result) +fmt.Printf("Remaining: %s\n", remaining.String()) +``` + ## Packages | Package | Description | @@ -292,6 +414,7 @@ for _, resp := range responses { | `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 diff --git a/src/pages/sdk/python/index.mdx b/src/pages/sdk/python/index.mdx index 67a359f2..e961883c 100644 --- a/src/pages/sdk/python/index.mdx +++ b/src/pages/sdk/python/index.mdx @@ -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="", + 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="", + 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="", + 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="", + 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="") + +# Update spending limit for a key-token pair: +call = AccountKeychain.update_spending_limit( + key_id="", + token=str(ALPHA_USD), + new_limit=2_000_000, +) + +# Replace all call scopes for a key: +call = AccountKeychain.set_allowed_calls( + key_id="", + scopes=[ + CallScope.transfer(target=ALPHA_USD), + CallScope.unrestricted(target=""), + ], +) + +# Remove a target contract from allowed call list: +call = AccountKeychain.remove_allowed_calls( + key_id="", + target="", +) + +# Query key info (read-only): +key_info = AccountKeychain.get_key( + w3, + account_address="", + key_id="", +) +print(key_info) +# {'signature_type': 0, 'key_id': '0x...', 'expiry': 1893456000, ...} + +# Query remaining spending limit: +remaining = AccountKeychain.get_remaining_limit( + w3, + account_address="", + key_id="", + 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=""),), +) + +signed_tx = tx.sign_access_key( # [!code hl] + access_key_private_key="", # [!code hl] + root_account="", # [!code hl] +) # [!code hl] +tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) +``` + ## Next Steps After setting up the Python SDK, you can: