Help us shape the future of Go! Take the 2026 Go Developer Survey

tempo

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT, Apache-2.0, MIT Imports: 20 Imported by: 0

Documentation

Overview

Package tempo contains the Tempo-specific charge method built on top of the generic pkg/client and pkg/server HTTP 402 flow, including request helpers, attribution utilities, and replay stores for single-process and Redis-backed deployments.

Index

Constants

View Source
const (
	// ChainIDMainnet is the chain ID of Tempo mainnet.
	ChainIDMainnet int64 = 4217
	// ChainIDModerato is the chain ID of the Tempo Moderato testnet.
	ChainIDModerato int64 = 42431
)

Tempo chain IDs. They have the type of the ChainID fields in the Tempo client and server configs.

View Source
const (
	// MethodName is the Tempo payment method token.
	MethodName = "tempo"
	// IntentCharge is the Tempo charge intent token. It is mpp.IntentCharge.
	IntentCharge = mpp.IntentCharge
	// DefaultDecimals is the default TIP-20 decimal precision used for charges.
	DefaultDecimals = 6
	// DefaultGasLimit is the fallback gas limit when estimation is unavailable.
	DefaultGasLimit = uint64(1_000_000)
	// SponsoredValidBeforeWindow is how far ahead of now a client sets the
	// expiring-nonce valid_before of a sponsored (fee-payer) charge
	// transaction, capped at FeePayerMinRemainingValidity past the second
	// after the challenge expiry. Servers require FeePayerMinRemainingValidity
	// of validity left when they verify, co-sign and broadcast, so a 60 second
	// window leaves about 45 seconds for clock skew between client and server,
	// signing and round trips. This is deliberately wider than mppx's 25
	// seconds, which leaves only about 10.
	SponsoredValidBeforeWindow = 60 * time.Second
	// FeePayerMinRemainingValidity is the minimum validity a sponsored charge
	// must keep: servers reject sponsored transactions with less remaining
	// before valid_before, refuse to issue sponsored challenges that expire
	// sooner than this after issuance, and clients refuse to sign sponsored
	// challenges that expire sooner than this.
	FeePayerMinRemainingValidity = 15 * time.Second
	// ReplayKeyPrefix is the storage namespace for charge replay protection.
	ReplayKeyPrefix = "mppx:charge:"
	// TransferSelector is the TIP-20 transfer selector.
	TransferSelector = "a9059cbb"
	// TransferWithMemoSelector is the Tempo transfer-with-memo selector.
	TransferWithMemoSelector = "95777d59"
	// TransferCalldataLength is the exact byte length of transfer(address,uint256).
	TransferCalldataLength = 4 + 32 + 32
	// TransferWithMemoCalldataLength is the exact byte length of transferWithMemo(address,uint256,bytes32).
	TransferWithMemoCalldataLength = 4 + 32 + 32 + 32
	// USDCEAddress is the bridged USDC (USDC.e) contract on Tempo mainnet.
	USDCEAddress = "0x20C000000000000000000000b9537d11c60E8b50"
	// OUSDAddress is the OpenUSD (OUSD) contract, deployed at the same address
	// on Tempo mainnet and Moderato.
	OUSDAddress = "0x20c0000000000000000000006a37DA5C996874BE"
	// PathUSDAddress is the pathUSD contract on Tempo networks.
	PathUSDAddress = "0x20c0000000000000000000000000000000000000"
)

Variables

View Source
var (
	// TransferTopic is the TIP-20 Transfer event topic hash.
	TransferTopic = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)"))
	// TransferWithMemoTopic is the Tempo TransferWithMemo event topic hash.
	TransferWithMemoTopic = crypto.Keccak256Hash([]byte("TransferWithMemo(address,address,uint256,bytes32)"))
)
View Source
var ExpiringNonceKey = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))

ExpiringNonceKey is the reserved nonce key used for fee-payer transactions.

Functions

func ChargeProofStoreKey

func ChargeProofStoreKey(challengeID string) string

ChargeProofStoreKey normalizes a replay-protection key for a Tempo proof credential.

func ChargeSponsoredTransactionStoreKey added in v0.4.0

func ChargeSponsoredTransactionStoreKey(hash string) string

ChargeSponsoredTransactionStoreKey normalizes a replay-protection key for a sponsored Tempo transaction awaiting co-signature, keyed by the keccak256 hash of the verified payer and canonical sender signing payload.

func ChargeStoreKey

func ChargeStoreKey(hash string) string

ChargeStoreKey normalizes a replay-protection key for a Tempo transaction hash.

func DefaultCurrenciesForChain added in v0.4.0

func DefaultCurrenciesForChain(chainID int64) []string

DefaultCurrenciesForChain returns the ordered stablecoins a server accepts by default on a chain. Mainnet offers OUSD, then USDC.e; Moderato offers OUSD, then pathUSD. Other chains fall back to the single DefaultCurrencyForChain token. The returned slice is a fresh copy.

func DefaultCurrencyForChain

func DefaultCurrencyForChain(chainID int64) string

DefaultCurrencyForChain returns the default stablecoin used for charges on a chain.

func EncodeAttribution

func EncodeAttribution(serverID, clientID, challengeID string) string

EncodeAttribution builds the 32-byte Tempo attribution memo for a Challenge.

func EncodeTransfer

func EncodeTransfer(recipient string, amount *big.Int) string

EncodeTransfer builds TIP-20 transfer calldata for the supplied recipient and amount.

func EncodeTransferWithMemo

func EncodeTransferWithMemo(recipient string, amount *big.Int, memo string) (string, error)

EncodeTransferWithMemo builds Tempo transfer-with-memo calldata for the supplied values.

func InferChainIDFromRPCURL

func InferChainIDFromRPCURL(rpcURL string) int64

InferChainIDFromRPCURL maps known Tempo RPC URLs back to their chain IDs. It returns 0 when the URL does not match a known Tempo endpoint.

func IsAttributionMemo

func IsAttributionMemo(memo string) bool

IsAttributionMemo reports whether a memo uses the Tempo attribution layout.

func IsKnownChainID

func IsKnownChainID(chainID int64) bool

IsKnownChainID reports whether the chain ID maps to a built-in Tempo network.

func MatchTransferCalldata

func MatchTransferCalldata(dataHex string, request ChargeRequest, realm, challengeID string) bool

MatchTransferCalldata reports whether calldata satisfies the canonical Tempo charge transfer.

func ParseHexBigInt

func ParseHexBigInt(value string) (*big.Int, error)

ParseHexBigInt decodes a JSON-RPC hex integer into a big.Int.

func ParseHexUint64

func ParseHexUint64(value string) (uint64, error)

ParseHexUint64 decodes a JSON-RPC hex integer into uint64. A bare "0x" (or an empty string) decodes to 0, matching ParseHexBigInt; some JSON-RPC nodes return "0x" for a zero-valued quantity.

func ParseTopicAddress

func ParseTopicAddress(topic string) string

ParseTopicAddress extracts the address stored in an indexed log topic.

func ProofSource

func ProofSource(chainID int64, address common.Address) string

ProofSource formats the canonical did:pkh source used by Tempo proof credentials.

func ProofTypedData added in v0.2.0

func ProofTypedData(chainID int64, account common.Address, challengeID, realm string) apitypes.TypedData

ProofTypedData builds the EIP-712 typed data for a Tempo zero-amount proof credential. The Proof struct includes the payer wallet (account) so the signature commits to a specific payer and cannot be replayed against a different account.

func ProofTypedDataHash

func ProofTypedDataHash(chainID int64, account common.Address, challengeID, realm string) (common.Hash, error)

ProofTypedDataHash returns the EIP-712 digest for a Tempo proof credential, bound to the payer wallet (account). See ProofTypedData.

func RPCURLForChain

func RPCURLForChain(chainID int64) (string, error)

RPCURLForChain returns the canonical Tempo RPC URL for a known chain ID. Chain ID 0 falls back to mainnet defaults.

func VerifyAttributionChallenge

func VerifyAttributionChallenge(memo, challengeID string) bool

VerifyAttributionChallenge reports whether a memo matches a specific Challenge ID.

func VerifyAttributionServer

func VerifyAttributionServer(memo, serverID string) bool

VerifyAttributionServer reports whether a memo matches the Challenge realm fingerprint.

Types

type ChargeCredentialPayload

type ChargeCredentialPayload struct {
	// Type selects the payload variant.
	Type CredentialType
	// Hash stores the submitted transaction hash for push flows.
	Hash string
	// Signature stores the raw transaction or proof signature.
	Signature string
}

ChargeCredentialPayload is the Tempo-specific payload carried by a Credential.

func ParseChargeCredentialPayload

func ParseChargeCredentialPayload(input map[string]any) (ChargeCredentialPayload, error)

ParseChargeCredentialPayload parses a generic payload map into a Tempo payload.

func (ChargeCredentialPayload) Map

func (p ChargeCredentialPayload) Map() map[string]any

Map converts a Tempo payload back into the generic Credential payload shape.

type ChargeMode

type ChargeMode string

ChargeMode declares which Tempo credential flow a challenge accepts.

const (
	// ChargeModePull allows clients to return a signed transaction credential.
	ChargeModePull ChargeMode = "pull"
	// ChargeModePush allows clients to broadcast first and return a hash credential.
	ChargeModePush ChargeMode = "push"
)

type ChargeRequest

type ChargeRequest struct {
	// Amount is the canonical base-unit amount.
	Amount string
	// Currency is the token contract address.
	Currency string
	// Recipient is the primary payee address.
	Recipient string
	// Description is a human-readable challenge description.
	Description string
	// ExternalID is application metadata echoed into receipts.
	ExternalID string
	// MethodDetails stores Tempo-specific extensions for the charge.
	MethodDetails MethodDetails
}

ChargeRequest is the canonical Tempo request shape embedded in a Challenge.

func NormalizeChargeRequest

func NormalizeChargeRequest(params ChargeRequestParams) (ChargeRequest, error)

NormalizeChargeRequest validates request parameters and produces the canonical Tempo shape.

func ParseChargeRequest

func ParseChargeRequest(input map[string]any) (ChargeRequest, error)

ParseChargeRequest parses a generic request map into the canonical Tempo shape.

func (ChargeRequest) Allows

func (r ChargeRequest) Allows(credentialType CredentialType) bool

Allows reports whether the request accepts the supplied credential type.

func (ChargeRequest) Map

func (r ChargeRequest) Map() map[string]any

Map converts a ChargeRequest into the generic request map embedded in a Challenge.

type ChargeRequestParams

type ChargeRequestParams struct {
	// Amount is the human-readable decimal amount.
	Amount string
	// Currency is the token contract address.
	Currency string
	// Recipient is the primary payee address.
	Recipient string
	// Decimals controls how Amount values are converted to base units.
	Decimals int
	// Description is copied into the challenge for display.
	Description string
	// ExternalID is opaque application metadata echoed into receipts.
	ExternalID string
	// ChainID binds the charge to a specific Tempo chain when set.
	ChainID int64
	// FeePayer enables the sponsored transaction flow.
	FeePayer bool
	// FeePayerURL points at a remote fee-payer signer.
	FeePayerURL string
	// Splits lists any additional transfers included in the charge.
	Splits []SplitParams
	// SupportedModes restricts which credential submission modes are allowed.
	SupportedModes []ChargeMode
}

ChargeRequestParams are the human-friendly inputs used to build a ChargeRequest.

type CredentialType

type CredentialType string

CredentialType identifies the Tempo credential payload shape.

const (
	// CredentialTypeTransaction carries a signed Tempo transaction.
	CredentialTypeTransaction CredentialType = "transaction"
	// CredentialTypeHash carries a submitted Tempo transaction hash.
	CredentialTypeHash CredentialType = "hash"
	// CredentialTypeProof carries a zero-amount proof signature.
	CredentialTypeProof CredentialType = "proof"
)

type ExpiringStore added in v0.5.1

type ExpiringStore interface {
	Store
	// PutIfAbsentUntil atomically stores value only when key is unused or its
	// previous entry has expired, and keeps the entry until expiresAt. It must
	// return false, storing nothing, when expiresAt is not in the future. An
	// entry must never be dropped before expiresAt, and Get must report it as
	// missing once it has expired.
	PutIfAbsentUntil(ctx context.Context, key, value string, expiresAt time.Time) (bool, error)
}

ExpiringStore is an optional extension of Store for replay keys that only need to outlive the credential they protect, like mppx's Store.tryClaim with a ReplayMarker expiry.

The Tempo charge verifier calls PutIfAbsentUntil instead of PutIfAbsent only when configured with ExpireReplayKeys, passing a moment after which the credential behind key can no longer be accepted. Otherwise, and for stores that do not implement it, replay keys are kept forever with PutIfAbsent. A store that embeds MemoryStore or RedisStore and overrides PutIfAbsent must also override PutIfAbsentUntil before enabling ExpireReplayKeys.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore is an opt-in development replay store. It does not protect across processes or restarts; production replicas must share a persistent Store. PutIfAbsent keeps keys until they are deleted; only keys stored with PutIfAbsentUntil expire.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore constructs an in-memory replay-protection store.

func (*MemoryStore) Delete

func (s *MemoryStore) Delete(_ context.Context, key string) error

Delete removes a replay-protection value.

func (*MemoryStore) Get

func (s *MemoryStore) Get(_ context.Context, key string) (string, bool, error)

Get loads a replay-protection value.

func (*MemoryStore) Put

func (s *MemoryStore) Put(_ context.Context, key, value string) error

Put stores a replay-protection value, replacing any existing entry.

func (*MemoryStore) PutIfAbsent

func (s *MemoryStore) PutIfAbsent(_ context.Context, key, value string) (bool, error)

PutIfAbsent stores a replay-protection value only when the key is unused.

func (*MemoryStore) PutIfAbsentUntil added in v0.5.1

func (s *MemoryStore) PutIfAbsentUntil(_ context.Context, key, value string, expiresAt time.Time) (bool, error)

PutIfAbsentUntil stores a replay-protection value only when the key is unused or expired, and keeps it until expiresAt. It stores nothing and returns false when expiresAt is not in the future.

type MethodDetails

type MethodDetails struct {
	// ChainID binds the charge to a specific Tempo chain when present.
	ChainID *int64
	// FeePayer enables the sponsored transaction flow.
	FeePayer bool
	// FeePayerURL points at a remote fee-payer signer.
	FeePayerURL string
	// Splits lists any additional transfers included in the charge.
	Splits []Split
	// SupportedModes restricts which credential submission modes are allowed.
	SupportedModes []ChargeMode
}

MethodDetails holds Tempo-specific request fields nested under methodDetails.

type RPCClient

type RPCClient interface {
	GetChainID(ctx context.Context) (uint64, error)
	GetTransactionCount(ctx context.Context, address string) (uint64, error)
	SendRawTransaction(ctx context.Context, serializedTx string) (string, error)
	SendRequest(ctx context.Context, method string, params ...interface{}) (*temporpc.JSONRPCResponse, error)
}

RPCClient is the subset of Tempo JSON-RPC used by the charge flow.

func NewRPCClient

func NewRPCClient(rpcURL string) RPCClient

NewRPCClient constructs a Tempo JSON-RPC client. Use NewRPCClientWithOptions to set a timeout, an *http.Client or credentials.

func NewRPCClientWithOptions added in v0.6.0

func NewRPCClientWithOptions(rpcURL string, opts ...temporpc.Option) RPCClient

NewRPCClientWithOptions constructs a Tempo JSON-RPC client with tempo-go client options, such as client.WithTimeout or client.WithHTTPClient from github.com/tempoxyz/tempo-go/pkg/client. Build it once and reuse it: pass it as the RPC field of the Tempo client or server config.

type RedisStore

type RedisStore struct {
	// contains filtered or unexported fields
}

RedisStore implements Store with Redis commands.

func NewRedisStore

func NewRedisStore(client redis.Cmdable, ttl time.Duration) *RedisStore

NewRedisStore constructs a Redis-backed replay-protection store.

When ttl is zero, stored values do not expire automatically. PutIfAbsent always keeps replay-protection keys persistent so used payment proofs and hashes cannot become valid again after a TTL window. Only PutIfAbsentUntil, which the Tempo charge verifier calls when configured with ExpireReplayKeys, stores keys that expire.

func (*RedisStore) Delete

func (s *RedisStore) Delete(ctx context.Context, key string) error

Delete removes a replay-protection value.

func (*RedisStore) Get

func (s *RedisStore) Get(ctx context.Context, key string) (string, bool, error)

Get loads a replay-protection value.

func (*RedisStore) Put

func (s *RedisStore) Put(ctx context.Context, key, value string) error

Put stores a replay-protection value, replacing any existing entry.

func (*RedisStore) PutIfAbsent

func (s *RedisStore) PutIfAbsent(ctx context.Context, key, value string) (bool, error)

PutIfAbsent stores a replay-protection value only when the key is unused.

func (*RedisStore) PutIfAbsentUntil added in v0.5.1

func (s *RedisStore) PutIfAbsentUntil(ctx context.Context, key, value string, expiresAt time.Time) (bool, error)

PutIfAbsentUntil stores a replay-protection value only when the key is unused, with SET NX EXAT, so Redis expires it at the absolute deadline expiresAt rather than after a TTL measured with this process's clock. It stores nothing and returns false when expiresAt is not in the future. EXAT requires Redis 6.2 or later.

type Split

type Split struct {
	// Amount is the base-unit amount routed to this split recipient.
	Amount string
	// Memo is an optional split-specific Tempo memo.
	Memo string
	// Recipient is the split recipient address.
	Recipient string
}

Split is a canonical additional transfer nested under methodDetails.splits.

type SplitParams

type SplitParams struct {
	// Amount is the human-readable amount routed to this split recipient.
	Amount string
	// Memo is an optional split-specific Tempo memo.
	Memo string
	// Recipient is the split recipient address.
	Recipient string
}

SplitParams are the human-friendly inputs used to build a canonical Split.

type Store

type Store interface {
	// Get loads a replay-protection value.
	Get(ctx context.Context, key string) (value string, ok bool, err error)
	// Put stores a replay-protection value, replacing any existing entry.
	Put(ctx context.Context, key, value string) error
	// PutIfAbsent stores a replay-protection value only when the key is unused.
	PutIfAbsent(ctx context.Context, key, value string) (bool, error)
	// Delete removes a replay-protection value.
	Delete(ctx context.Context, key string) error
}

Store is the minimal replay-protection contract used by the Tempo charge verifier.

Unlike the verifier-specific helpers elsewhere in this package, Store stays close to common key-value backends so in-memory, Redis, and SQL-backed implementations can all satisfy the same interface. Implementations must not allow PutIfAbsent replay keys to become reusable after later expiry.

Directories

Path Synopsis
Package chargeclient contains the Tempo charge client method for creating Payment Credentials that satisfy Tempo HTTP 402 Challenges.
Package chargeclient contains the Tempo charge client method for creating Payment Credentials that satisfy Tempo HTTP 402 Challenges.
internal
rpccache
Package rpccache reuses the Tempo JSON-RPC clients that the charge client and server build from URLs, so each is built once rather than per request.
Package rpccache reuses the Tempo JSON-RPC clients that the charge client and server build from URLs, so each is built once rather than per request.
Package chargeserver contains the Tempo charge server method and verifier.
Package chargeserver contains the Tempo charge server method and verifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL