Skip to main content
Version: Next

Claim permits

A claim permit is a signature that authorizes one action without the signer submitting a transaction. The signing authority signs the action, an executor submits it in one transaction, and the executor pays the standard network fee. The signer needs no ucen and no transaction of its own.

This is the single-transaction path. The two-transaction path is the authorization, where the holder signs TX1 and the executor settles TX2 later. The two are separate: a claim permit is not a compressed authorization, and it does not create authorization state.

The implementation is in tokenfactory.

The messages​

MessageSigning authorityEffect
MsgClaimMintThe denom admin (owner)Mints amount of denom to mint_to.
MsgClaimSendThe balance holder (from)Sends amount of denom to to.
MsgClaimMintNFTThe class issuerMints a certificate into to and returns the new token_id.
MsgClaimSendNFTThe current certificate holder (from)Transfers the certificate to to.
MsgRevokeClaimThe signerPermanently invalidates an unused nonce.

MsgRevokeClaim is a normal transaction signed by the signer. It carries no permit signature: the transaction signature is the authorization.

The flow​

The signature contract​

The permit is not a Cosmos SignDoc signature. It is a separate signature over a deterministic payload:

ElementValue
Curvesecp256k1
HashSHA-256 over the payload
Signature64-byte compact R‖S, lower-S; a high-S signature is rejected
Public key33-byte compressed secp256k1
AddressRIPEMD160(SHA256(pubkey)), bech32-encoded with the chain prefix
Signed fieldsEvery field except executor and signature
EncodingEach field is uint32_be(length) ‖ bytes; fields are concatenated in a fixed order

The payload starts with a domain tag, so a signature for one message type cannot be replayed as another:

MessageDomain tag
MsgClaimMintcenterchain/tokenfactory/claim-mint
MsgClaimSendcenterchain/tokenfactory/claim-send
MsgClaimMintNFTcenterchain/tokenfactory/claim-mint-nft
MsgClaimSendNFTcenterchain/tokenfactory/claim-send-nft
MsgExecuteMint action permitcenterchain/tokenfactory/execute-mint
MsgExecuteSend action permitcenterchain/tokenfactory/execute-send

Field order​

MessageFields, in order
MsgClaimMintowner, denom, amount, mint_to, nonce, chain_id, expiry_height, pubkey
MsgClaimSendfrom, denom, amount, to, nonce, chain_id, expiry_height, pubkey
MsgClaimMintNFTissuer, class_id, to, uri, uri_hash, nonce, chain_id, expiry_height, pubkey
MsgClaimSendNFTfrom, class_id, token_id, to, nonce, chain_id, expiry_height, pubkey
MsgExecuteMintholder, denom, amount, mint_to, nonce, chain_id, expiry_height, pubkey
MsgExecuteSendholder, denom, amount, to, nonce, chain_id, expiry_height, pubkey

nonce is 16 raw bytes. expiry_height is an 8-byte big-endian uint64. pubkey is 33 raw bytes. Strings are UTF-8. Amounts are canonical decimal strings, with no leading zeros.

field(x) = uint32_be(len(x)) || x
payload = field(domain) || field(field_1) || ... || field(field_n)
signature = secp256k1_sign(sha256(payload), private_key)

The public key must resolve to the address named as the signing authority. A signature cannot be attributed to an address that did not produce it.

Action permits for ExecuteMint and ExecuteSend​

The MINT and SEND authorizations are gas-only. The action itself is authorized by a separate permit carried inside MsgExecuteMint or MsgExecuteSend. That permit follows the same signature contract, but its holder field is the TX1 fee payer and the transaction signer is the executor who performs the action.

MessageAction signerGuard
MsgExecuteMintThe denom adminThe signer must be the denom admin.
MsgExecuteSendThe balance holderThe signer is the from of the bank send.

The action permit is checked independently of the TX1 gas authorization. TX1 cannot change the denom, amount, or recipient in the action permit.

Replay and freshness​

AxisMechanism
Single useA 16-byte nonce per signer. The chain stores claim/used/{signer}/{nonce} in the same transaction as the action.
Cross-chainchain_id must equal the executing chain.
Expiryexpiry_height is absolute. A permit is rejected once the current height is greater than it.
Far-future expiryexpiry_height may be at most max_claim_expiry_blocks ahead of the executing height. The default is 14,400 blocks.
PurposeThe domain tag separates mint, send, NFT mint, and NFT send.
AttributionThe public key must hash to the claimed signer address.
RevocationMsgRevokeClaim writes a permanent tombstone for an unused nonce.
AtomicityThe nonce marker and the action commit in the same transaction. If the action fails, both roll back and the nonce remains unused.

Used-nonce markers are pruned after their expiry height. Revocation tombstones are permanent, because the chain cannot know how far in the future a permit for that nonce may have been signed. Both markers are exported in genesis and restored on import; a new chain must use a new chain_id so permits signed for the old chain cannot execute.

Gas​

The executor pays the standard transaction fee. The signer pays nothing and needs no ucen balance. This is different from the two-phase path, where the holder named in the authorization pays the TX2 fee.

Guards​

A claim permit is not a bypass:

  • A fungible mint requires the signer to be the denom admin.
  • An NFT mint requires the signer to be the class issuer.
  • A fungible send is a normal bank send: blacklist and restricted-denom rules still apply.
  • An NFT send re-runs the lock, blocklist, and restricted-class checks.
  • A recipient cannot be a module account.
  • Amounts must be positive and canonical.
  • An NFT mint enforces the class-id, URI, and URI-hash limits.

Limits​

FieldLimit
Nonce16 bytes
Public key33 bytes, compressed
Signature64 bytes, lower-S
Chain ID1 to 128 characters
ExpiryNon-zero, and at most max_claim_expiry_blocks ahead
Class ID1 to 256 bytes
Token ID1 to 256 bytes
URIAt most 512 bytes
URI hashAt most 128 bytes

Client checklist​

  1. Build the payload with the exact field order and the correct domain tag.
  2. Length-prefix every field with a big-endian uint32.
  3. Hash with SHA-256 and sign with secp256k1, producing a 64-byte R‖S with lower-S.
  4. Send the 33-byte compressed public key, and confirm it hashes to the signer address.
  5. Choose a fresh 16-byte nonce per signer. Reusing one fails.
  6. Set expiry_height to an absolute block height, no more than max_claim_expiry_blocks ahead.
  7. Set chain_id to the executing chain. A permit for another chain is rejected.
  8. Let the executor submit the message and pay the fee.
  9. If a permit may have leaked, revoke its nonce before it is spent.