Context
-
I have an Internet Identity–based dapp. Previously each user got an EVM address derived via canister tECDSA and could:
-
send USDC on Base with a normal EVM tx (pays ETH gas), or
-
send gaslessly using USDC EIP-3009
transferWithAuthorization(I/relayer pay ETH).
-
-
This works end-to-end already.
Goal (what I want now)
Move custody from individuals to multiple Motoko canisters (each canister owns one EVM address via tECDSA) and still enable gasless USDC sends on Base. The canister owner triggers transfers; users shouldn’t need ETH.
Canister API I’m exposing (update calls only)
public shared ({ caller }) func ethereum_pubkey() : async Text; // 0x-hex SEC1 pubkey
public shared ({ caller }) func new_nonce() : async Text; // 0x-hex 32B nonce (from raw_rand)
public shared ({ caller }) func sign_digest(digest32 : Blob) : async Text; // 0x[r||s] (64B)
public shared ({ caller }) func relay_submit(json : Text)
: async { status : Nat; body : Text }; // optional HTTPS outcall to my relayer
-
Derivation path is tied to the canister (not caller):
["custody:usdc:v1", <canister_principal_bytes>]on mainnet -
EIP-712 hashing (domain/types/message for USDC Base, chainId 8453, token
0x833589f...2913) is done client-side with ethers to keep the canister simple and fast. -
For signing, the canister calls
sign_with_ecdsaand returns0x[r||s]; I recovervclient-side and broadcast either from an admin wallet or via a relayer endpoint (optionally usingrelay_submitHTTPS outcall).
What I need help with
-
Recommended pattern for gasless USDC transaction from a canister on Base (EIP-3009):
-
What is the recommended architecture to enable a canister to custody and transfer USDC on Base, with actions authorized by a single owner or N-of-M multisig, while delivering a gasless user experience through admin-sponsored fees
-
Any references or open-source relayer examples people use with USDC EIP-3009 on Base?
-
-
Cycle budgeting guidance (realistic numbers on mainnet):
-
ecdsa_public_keyandsign_with_ecdsa: I’m seeing ~26 Gcycles for a sign. What buffers do you use in prod? -
http_requestoutcalls to a relayer (small JSON POST): what cycle budget is reasonable per call?
-
-
Nonce & replay protection best practices:
-
Beyond storing used nonces in stable memory and rejecting duplicates, anything else you recommend?
-
Do you also pin
chainId=8453andverifyingContract(USDC Base) in the canister, and persist a minimal audit log (from/to/value/nonce/validBefore)?
-
-
Multi-canister setup tips:
- I plan to run many custody canisters, each with its own tECDSA-derived address . Any pitfalls, quota considerations, or scaling tips for tECDSA across many canisters?
Constraints / intent
-
Must be Base mainnet (chainId 8453) and USDC token (
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913). -
End-user must be gasless; I’m fine paying ETH (on Base) via admin wallet or relayer.
-
I’d like to keep the on-chain Motoko side minimal (no on-canister EVM tx building), but I’m open to adding hashing/validation if there are good libraries.
Thanks in advance for any pointers, example code, cycle numbers, or “don’t do this” gotchas from teams who’ve shipped gasless EIP-3009 with tECDSA!