CIS-5: Smart Contract Wallet Standard¶
Created |
Mar 17, 2024 |
---|---|
Final |
May 23, 2024 |
Supported versions |
Smart contract version 1 or newer
(Protocol version 4 or newer)
|
Standard identifier |
|
Requires |
Abstract¶
A standard interface for defining a smart contract wallet that can hold and transfer CCDs and CIS-2 tokens. CCDs/CIS-2 tokens can be deposited into the smart contract wallet by specifying to which public key the deposit should be assigned.
The holder of the corresponding private key does not submit transactions on chain to transfer/withdraw its CCD/CIS-2 token balances, but instead, it can generate a valid signature, identify a willing third party to submit its signature in a transaction on-chain (a service fee can be added to financially incentivize a third party to do so).
The three main actions in the smart contract that can be taken:
deposit: assigns the balance to a public key within the smart contract wallet.
internal transfer: assigns the balance to a new public key within the smart contract wallet.
withdraw: withdraws the balance out of the smart contract wallet to a native account or smart contract.
The goal of this standard is to create an alternative simplified onboarding flow without the inconvenience of a native account creation on Concordium. Allowing for CIS-5 smart contract wallets to be supported as first-class citizens in Concordium wallets and tooling.
Specification¶
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
General types and serialization¶
Note
Integers are encoded in little-endian unless stated otherwise.
TokenID
¶
The token ID is from the CIS-2 standard TokenID.
A token ID is serialized as 1 byte for the size (n
) of the identifier, followed by this number of bytes for the token id (id
):
TokenID ::= (n: Byte) (id: Byteⁿ)
TokenAmount
¶
The token amount is from the CIS-2 standard TokenAmount. It is serialized using the LEB128 variable-length unsigned integer encoding, with the additional constraint that the total number of bytes of the encoding MUST not exceed 37 bytes:
TokenAmount ::= (x: Byte) => x if x < 2^7
| (x: Byte) (m: TokenAmount) => (x - 2^7) + 2^7 * m if x >= 2^7
CIS5-ExternalTokenAmount
¶
A CIS-2 token amount on a blockchain defined by the amount, token id and smart contract it represents.
It is serialized as a TokenAmount (tokenAmount
), a TokenID (tokenId
), and a ContractAddress (cis2TokenContractAddress
):
ExternalTokenAmount ::= (tokenAmount: TokenAmount) (tokenId: TokenId) (cis2TokenContractAddress: ContractAddress)
AccountAddress
¶
An address of an account.
It is serialized as 32 bytes:
AccountAddress ::= (address: Byte³²)
ContractAddress
¶
An address of a contract instance. It consists of an index and a subindex, both unsigned 64-bit integers.
It is serialized as: First 8 bytes for the index (index
) followed by 8 bytes for the subindex (subindex
), both little-endian:
ContractAddress ::= (index: Byte⁸) (subindex: Byte⁸)
Address
¶
Is either an AccountAddress or a ContractAddress.
It is serialized as: The first byte indicates whether it is an account address or a contract address.
In case the first byte is 0 then an AccountAddress (address
) follows.
In case the first byte is 1 then a ContractAddress (address
) follows:
Address ::= (0: Byte) (address: AccountAddress)
| (1: Byte) (address: ContractAddress)
EntrypointName
¶
A name for a smart contract function entrypoint.
It is serialized as: First 2 bytes encode the length (n
) of the entrypoint name in little-endian, followed by this many bytes for the entrypoint name (entrypoint
):
EntrypointName ::= (n: Byte²) (entrypoint: Byteⁿ)
Timestamp
¶
A timestamp given in milliseconds since Unix epoch. It consists of an unsigned 64-bit integer.
It is serialized as 8 bytes in little-endian:
Timestamp ::= (milliseconds: Byte⁸)
Nonce
¶
An unsigned 64-bit integer number that increases sequentially to protect against replay attacks.
It is serialized as 8 bytes in little-endian:
Nonce ::= (nonce: Byte⁸)
CCDAmount
¶
An unsigned 64-bit integer number.
It is serialized as 8 bytes in little-endian:
CCDAmount ::= (ccdAmount: Byte⁸)
PublicKeyEd25519
¶
An Ed25519 public key is represented as a 32-byte array.
It is serialized as 32 bytes:
PublicKeyEd25519 ::= (key: Byte³²)
SignatureEd25519
¶
Signature for an Ed25519 message.
It is serialized as 64 bytes:
SignatureEd25519 ::= (signature: Byte⁶⁴)
ChainContext
¶
The chain context consists of the genesis hash and the contract address to prevent re-playing signatures across the testnet/mainnet chain and smart contracts.
It is serialized as: First 32 bytes defining the genesisHash (its hex string representation converted into bytes), followed by 8 bytes for the index of the smart contract address and 8 bytes for the subindex of the smart contract address:
ChainContext ::= (genesisHash: Byte³²) (index: Byte⁸) (subindex: Byte⁸)
Logged events¶
The event defined by this specification is serialized using one byte to discriminate it from other events logged by the smart contract. Other events logged by the smart contract SHOULD NOT have a first byte colliding with the event defined by this specification.
NonceEvent
¶
A NonceEvent
SHALL be logged every time a signature is successfully processed and considered valid by the contract.
The NonceEvent
is serialized as: First a byte with the value of 250, followed by the Nonce (nonce
) that is used in the SigningData, and an PublicKeyEd25519 (sponsoree
):
NonceEvent ::= (250: Byte) (nonce: Nonce) (sponsoree: PublicKeyEd25519)
DepositCcdEvent
¶
A DepositCcdEvent
SHALL be logged every time an amount of CCD received by the contract is assigned to a public key.
The DepositCcdEvent
is serialized as: First a byte with the value of 249, followed by the CCDAmount (ccdAmount
), the Address (from
), and a PublicKeyEd25519 (to
):
DepositCcdEvent ::= (249: Byte) (ccdAmount: CCDAmount) (from: Address) (to: PublicKeyEd25519)
DepositCis2TokensEvent
¶
A DepositCis2TokensEvent
SHALL be logged every time a token amount received by the contract is assigned to a public key.
The DepositCis2TokensEvent
is serialized as: First a byte with the value of 248, followed by the
TokenAmount (tokenAmount
), TokenID (TokenID
),
ContractAddress (cis2TokenContractAddress
), the Address (from
), and a PublicKeyEd25519 (to
):
DepositCis2TokensEvent ::= (248: Byte) (tokenAmount: TokenAmount) (tokenId: TokenID) (cis2TokenContractAddress: ContractAddress) (from: Address) (to: PublicKeyEd25519)
WithdrawCcdEvent
¶
A WithdrawCcdEvent
SHALL be logged every time an amount of CCD held by a public key is withdrawn to an address.
The WithdrawCcdEvent
is serialized as: First a byte with the value of 247, followed by the CCDAmount (ccdAmount
), a PublicKeyEd25519 (from
), and the Address (to
):
WithdrawCcdEvent ::= (247: Byte) (ccdAmount: CCDAmount) (from: PublicKeyEd25519) (to: Address)
WithdrawCis2TokensEvent
¶
A WithdrawCis2TokensEvent
SHALL be logged every time a token amount held by a public key is withdrawn to an address.
The WithdrawCis2TokensEvent
is serialized as: First a byte with the value of 246, followed by the
TokenAmount (tokenAmount
), TokenID (TokenID
),
ContractAddress (cis2TokenContractAddress
), a PublicKeyEd25519 (from
), and the Address (to
):
WithdrawCis2TokensEvent ::= (246: Byte) (tokenAmount: TokenAmount) (tokenId: TokenID) (cis2TokenContractAddress: ContractAddress) (from: PublicKeyEd25519) (to: Address)
TransferCcdEvent
¶
A TransferCcdEvent
SHALL be logged every time an amount of CCD held by a public key is transferred to another public key within the contract.
The TransferCcdEvent
is serialized as: First a byte with the value of 245, followed by the CCDAmount (ccdAmount
), a PublicKeyEd25519 (from
), and the PublicKeyEd25519 (to
):
TransferCcdEvent ::= (245: Byte) (ccdAmount: CCDAmount) (from: PublicKeyEd25519) (to: PublicKeyEd25519)
TransferCis2TokensEvent
¶
A TransferCis2TokensEvent
SHALL be logged every time a token amount held by a public key is transferred to another public key within the contract.
The TransferCis2TokensEvent
is serialized as: First a byte with the value of 244, followed by the
TokenAmount (tokenAmount
), TokenID (TokenID
),
ContractAddress (cis2TokenContractAddress
), a PublicKeyEd25519 (from
), and the PublicKeyEd25519 (to
):
TransferCis2TokensEvent ::= (244: Byte) (tokenAmount: TokenAmount) (tokenId: TokenID) (cis2TokenContractAddress: ContractAddress) (from: PublicKeyEd25519) (to: PublicKeyEd25519)
Note
The collection of all CIS-5 events emitted by a smart contract instance implementing CIS-5 SHALL enable off-chain applications to compute off-chain all non-zero balances that the public keys are holding.
Contract functions¶
A smart contract implementing this standard MUST export the following functions:
depositCcd
¶
The function is payable and deposits/assigns the send CCDAmount to a public key (PublicKeyEd25519
).
Parameter¶
The parameter is a PublicKeyEd25519
.
See the serialization rules in PublicKeyEd25519.
depositCis2Tokens
¶
This function SHOULD be called through the receive hook mechanism (Receive hook function)
of a CIS-2 token contract. The function deposits/assigns the send CIS-2 token amount to a public key (PublicKeyEd25519
).
Note
The depositCis2Tokens
function can be called by any smart contract. It is up to the exact implementation of the smart contract wallet whether it should trust the caller or not.
The smart contract wallet is not required to check if the invoking contract is a CIS-2 token contract or has some reasonable receive hook logic implemented.
If no additional authorization is added to this function, similar caution should be applied as if you would directly interact with any CIS-2 token contract.
Only interact with a CIS-2 token contract or value its recorded token balance if you checked its smart
contract logic or reasonable social reputation is given to the project/CIS-2 token contract.
Parameter¶
The parameter is the Receive hook parameter (OnReceivingCis2Params
) and the
data
field of the OnReceivingCis2Params
SHALL encode a PublicKeyEd25519
.
See the serialization rules in Receive hook parameter and the serialization rules in PublicKeyEd25519.
Requirements¶
The function SHOULD check that a contract is the caller since only a contract can implement a receive hook mechanism.
withdrawCcd
¶
The function executes a list of token withdrawals of CCDs to native accounts and/or smart contracts out of the smart contract wallet. When transferring CCD to a contract address, a CCD receive hook function MUST be triggered.
Parameter¶
The parameter is a list of withdrawals.
It is serialized as: 2 bytes representing the number of withdrawals (n
) followed by the bytes for this number of withdrawals
.
Each WithdrawBatchCcdAmount
is serialized as: a PublicKeyEd25519 (signer
), a SignatureEd25519 (signature
), and a WithdrawMessageCcdAmount
(message
).
Each WithdrawMessageCcdAmount
is serialized as: an EntrypointName (entryPoint
), a Timestamp (expiryTime
), a Nonce (nonce
), a PublicKeyEd25519 (serviceFeeRecipient
), a CCDAmount (serviceFee
), 2 bytes representing the number of simple withdraws (m
) followed by the bytes for this number of simple withdraws (simpleWithdraws
).
Each WithdrawCcdAmount
is serialized as: the receiving address Receiver (to
), the CCDAmount (withdrawAmount
), and some additional data AdditionalData (data
):
WithdrawCcdAmount ::= (to: Receiver) (withdrawAmount: CCDAmount) (data: AdditionalData)
WithdrawMessageCcdAmount ::= (entryPoint: EntrypointName) (expiryTime: Timestamp) (nonce: Nonce) (serviceFeeRecipient: PublicKeyEd25519) (serviceFee: CCDAmount) (m: Byte²) (simpleWithdraws: WithdrawCcdAmountᵐ)
WithdrawBatchCcdAmount ::= (signer: PublicKeyEd25519) (signature: SignatureEd25519) (message: WithdrawMessageCcdAmount)
WithdrawParameterCcdAmount ::= (n: Byte²) (withdrawals: WithdrawBatchCcdAmountⁿ)
CCD Receive hook parameter¶
The parameter for the CCD receive hook function contains some additional data bytes.
It is serialized as several bytes:
CCDReceiveHookParameter ::= (data: Byteᵐ)
Generating a valid signature¶
To generate a valid signature for the entry point, the following bytes have to be signed:
WithdrawCCDSigningData ::= (chainContext: ChainContext) (param: WithdrawParameterCcdAmount)
Requirements¶
The list of withdrawals MUST be executed in order.
The contract function MUST reject if any of the withdrawals fail to be executed.
The function MUST reject if the signature verification fails for any withdrawal.
The function MUST fail if the CCD balance of the
signer
is insufficient to do the withdrawal for any withdrawal.A function MUST non-strictly decrease the CCD balance of the
signer```s public key and non-strictly increase the balance of the ``to
address or fail for any withdrawal.A withdrawal back to this contract into the
depositCcd
entrypoint MUST be executed as a normal withdrawal.A withdrawal of a CCD amount of zero MUST be executed as a normal withdrawal.
A withdrawal of any amount of CCD to a contract address MUST call a CCD receive hook function on the receiving smart contract with a ccd receive hook parameter.
The contract function MUST reject if the CCD receive hook function called on the contract receiving CCDs rejects for any withdrawal.
The balance of a public key not owning any CCD amount SHOULD be treated as having a balance of zero.
The function MUST transfer the
serviceFee
to theserviceFeeRecipient
for each batch withdrawal ifserviceFee!=0
.
Warning
Be aware of transferring CCDs to a non-existing account address or contract address which results in an error on Concordium. Checking the existence of an account address/ contract address would ideally be done off-chain before the message is even sent to the smart contract.
withdrawCis2Tokens
¶
The function executes a list of token withdrawals to native accounts and/or smart contracts out of the smart contract wallet.
This function MUST call the transfer
function on the CIS-2 token contract for every withdrawal.
Parameter¶
The parameter is a list of withdrawals.
It is serialized as: 2 bytes representing the number of withdrawals (n
) followed by the bytes for this number of withdrawals
.
Each WithdrawBatchTokenAmount
is serialized as: a PublicKeyEd25519 (signer
), a SignatureEd25519 (signature
), and a WithdrawMessageTokenAmount
(message
).
Each WithdrawMessageTokenAmount
is serialized as: an EntrypointName (entryPoint
), a Timestamp (expiryTime
), a Nonce (nonce
), a PublicKeyEd25519 (serviceFeeRecipient
), a CIS5-ExternalTokenAmount (serviceFee
), 2 bytes representing the number of simple withdraws (m
) followed by the bytes for this number of simple withdraws (simpleWithdraws
).
Each WithdrawTokenAmount
is serialized as: the receiving address Receiver (to
), the CIS5-ExternalTokenAmount (withdrawAmount
), and some additional data AdditionalData (data
):
WithdrawTokenAmount ::= (to: Receiver) (withdrawAmount: ExternalTokenAmount) (data: AdditionalData)
WithdrawMessageTokenAmount ::= (entryPoint: EntrypointName) (expiryTime: Timestamp) (nonce: Nonce) (serviceFeeRecipient: PublicKeyEd25519) (serviceFee: ExternalTokenAmount) (m: Byte²) (simpleWithdraws: WithdrawTokenAmountᵐ)
WithdrawBatchTokenAmount ::= (signer: PublicKeyEd25519) (signature: SignatureEd25519) (message: WithdrawMessageTokenAmount)
WithdrawParameterTokenAmount ::= (n: Byte²) (withdrawals: WithdrawBatchTokenAmountⁿ)
Generating a valid signature¶
To generate a valid signature for the entry point, the following bytes have to be signed:
WithdrawTokensSigningData ::= (chainContext: ChainContext) (param: WithdrawParameterTokenAmount)
Requirements¶
The list of withdrawals MUST be executed in order.
The contract function MUST reject if any of the withdrawals fail to be executed.
The function MUST reject if the signature verification fails for any withdrawal.
This function MUST call the
transfer
function on the CIS-2 token contract for every withdrawal.The function MUST fail if the token balance of the
signer
is insufficient to do the withdrawal for any withdrawal.A function MUST non-strictly decrease the token balance of the
signer
public key and non-strictly increase the balance of theto
address or fail for any withdrawal.A withdrawal back to this contract into the
depositCis2Tokens
entrypoint MUST be executed as a normal withdrawal.A withdrawal of a token amount of zero MUST be executed as a normal withdrawal.
The balance of a public key not owning any tokens SHOULD be treated as having a balance of zero.
The function MUST transfer the
serviceFee
to theserviceFeeRecipient
for each batch withdrawal ifserviceFee!=0
.
transferCcd
¶
The function executes a list of CCD transfers to public keys within the smart contract wallet.
Parameter¶
The parameter is a list of transfers.
It is serialized as: 2 bytes representing the number of transfers (n
) followed by the bytes for this number of transfers
.
Each TransferBatchCcdAmount
is serialized as: a PublicKeyEd25519 (signer
), a SignatureEd25519 (signature
), and a TransferMessageCcdAmount
(message
).
Each TransferMessageCcdAmount
is serialized as: an EntrypointName (entryPoint
), a Timestamp (expiryTime
), a Nonce (nonce
), a PublicKeyEd25519 (serviceFeeRecipient
), a CCDAmount (serviceFee
), 2 bytes representing the number of simple transfers (m
) followed by the bytes for this number of simple transers (simpleTransfers
).
Each TransferCcdAmount
is serialized as: the receiving PublicKeyEd25519 (to
), and the CCDAmount (transferAmount
):
TransferCcdAmount ::= (to: PublicKeyEd25519) (transferAmount: CCDAmount)
TransferMessageCcdAmount ::= (entryPoint: EntrypointName) (expiryTime: Timestamp) (nonce: Nonce) (serviceFeeRecipient: PublicKeyEd25519) (serviceFee: CCDAmount) (m: Byte²) (simpleTransfers: TransferCcdAmountᵐ)
TransferBatchCcdAmount ::= (signer: PublicKeyEd25519) (signature: SignatureEd25519) (message: TransferMessageCcdAmount)
TransferParameterCcdAmount ::= (n: Byte²) (transfers: TransferBatchCcdAmount)
Generating a valid signature¶
To generate a valid signature for the entry point, the following bytes have to be signed:
TransferCCDSigningData ::= (chainContext: ChainContext) (param: TransferParameterCcdAmount)
Requirements¶
The function MUST reject if the signature verification fails for any transfer.
The function MUST fail if the CCD balance of the
signer
is insufficient to do the transfer for any transfer.A function MUST non-strictly decrease the CCD balance of the
signer
public key and non-strictly increase the balance of theto
public key or fail for any transfer.A transfer of a CCD amount of zero MUST be executed as a normal transfer.
The balance of a public key not owning any CCD amount SHOULD be treated as having a balance of zero.
The function MUST transfer the
serviceFee
to theserviceFeeRecipient
for each batch transfer ifserviceFee!=0
.
transferCis2Tokens
¶
The function executes a list of token transfers to public keys within the smart contract wallet.
Parameter¶
The parameter is a list of transfers.
It is serialized as: 2 bytes representing the number of transfers (n
) followed by the bytes for this number of transfers
.
Each TransferBatchTokenAmount
is serialized as: a PublicKeyEd25519 (signer
), a SignatureEd25519 (signature
), and a TransferMessageTokenAmount
(message
).
Each TransferMessageTokenAmount
is serialized as: an EntrypointName (entryPoint
), a Timestamp (expiryTime
), a Nonce (nonce
), a PublicKeyEd25519 (serviceFeeRecipient
), a CIS5-ExternalTokenAmount (serviceFee
), 2 bytes representing the number of simple transfers (m
) followed by the bytes for this number of simple transfers (simpleTransfers
).
Each TransferTokenAmount
is serialized as: the receiving PublicKeyEd25519 (to
), the CIS5-ExternalTokenAmount (transferAmount
):
TransferTokenAmount ::= (to: PublicKeyEd25519) (transferAmount: ExternalTokenAmount)
TransferMessageTokenAmount ::= (entryPoint: EntrypointName) (expiryTime: Timestamp) (nonce: Nonce) (serviceFeeRecipient: PublicKeyEd25519) (serviceFee: ExternalTokenAmount) (m: Byte²) (simpletransfers: TransferTokenAmountᵐ)
TransferBatchTokenAmount ::= (signer: PublicKeyEd25519) (signature: SignatureEd25519) (message: TransferMessageTokenAmount)
TransferParameterTokenAmount ::= (n: Byte²) (transfers: TransferBatchTokenAmountⁿ)
Generating a valid signature¶
To generate a valid signature for the entry point, the following bytes have to be signed:
TransferTokensSigningData ::= (chainContext: ChainContext) (param: TransferParameterTokenAmount)
Requirements¶
The function MUST reject if the signature verification fails for any of the transfers.
The function MUST fail if the token balance of the
signer
is insufficient to do the transfer for any transfer.A function MUST non-strictly decrease the token balance of the
signer
public key and non-strictly increase the balance of theto
public key or fail for any transfer.A transfer of a token amount of zero MUST be executed as a normal transfer.
The balance of a public key not owning any tokens SHOULD be treated as having a balance of zero.
The function MUST transfer the
serviceFee
to theserviceFeeRecipient
for each batch transfer ifserviceFee!=0
.
ccdBalanceOf
¶
The function queries the CCD balances of a list of public keys.
Parameter¶
The parameter consists of a list of public keys.
It is serialized as: 2 bytes for the number of public keys (n
) and then this number of PublicKeyEd25519 (publicKeys
):
CCDBalanceOfParameter ::= (n: Byte²) (publicKeys: PublicKeyEd25519ⁿ)
Response¶
The function output response is a list of CCD amounts.
It is serialized as: 2 bytes for the number of CCD amounts (n
) and then this number of CCDAmount (results
):
CCDBalanceOfResponse ::= (n: Byte²) (results: CCDAmountⁿ)
Requirements¶
The balance of a public key not owning any CCD SHOULD be treated as having a balance of zero.
The number of results in the response MUST correspond to the number of the public keys in the parameter.
The order of results in the response MUST correspond to the order of public keys in the parameter.
The contract function MUST NOT increase or decrease the CCD balance or token balance of any public key for any token type.
cis2BalanceOf
¶
The function queries the token balances of a list of public keys for given token IDs, and CIS-2 token contract addresses.
Parameter¶
The parameter consists of a list of token ID, CIS-2 token contract address, and public key triplets.
It is serialized as: 2 bytes for the number of queries (n
) and then this number of queries (queries
).
A query is serialized as a TokenID (tokenID
), a ContractAddress (cis2TokenContractAddress
), and a PublicKeyEd25519 (publicKey
):
Cis2TokensBalanceOfQuery ::= (tokenID: TokenID) (cis2TokenContractAddress: ContractAddress) (publicKey: PublicKeyEd25519)
Cis2TokensBalanceOfParameter ::= (n: Byte²) (queries: Cis2TokensBalanceOfQueryⁿ)
Response¶
The function output response is a list of token amounts.
It is serialized as: 2 bytes for the number of token amounts (n
) and then this number of TokenAmount (results
):
Cis2TokensBalanceOfResponse ::= (n: Byte²) (results: TokenAmountⁿ)
Requirements¶
The balance of a public key not owning any amount of a token type SHOULD be treated as having a balance of zero.
The number of results in the response MUST correspond to the number of the queries in the parameter.
The order of results in the response MUST correspond to the order of queries in the parameter.
The contract function MUST NOT increase or decrease the CCD balance or token balance of any public key for any token type.