diff --git a/Cargo.lock b/Cargo.lock index 37c757d6e..6b0a91b31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "arrayvec" version = "0.5.2" @@ -241,6 +243,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155" +version = "0.6.0-alpha1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw0", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "0.6.0-alpha1" diff --git a/packages/cw0/src/event.rs b/packages/cw0/src/event.rs new file mode 100644 index 000000000..79abed5a5 --- /dev/null +++ b/packages/cw0/src/event.rs @@ -0,0 +1,7 @@ +use cosmwasm_std::Response; + +/// This defines a set of attributes which should be added to `Response`. +pub trait Event { + /// Append attributes to response + fn add_attributes(&self, response: &mut Response); +} diff --git a/packages/cw0/src/lib.rs b/packages/cw0/src/lib.rs index 96278b8ef..5fac7f7f6 100644 --- a/packages/cw0/src/lib.rs +++ b/packages/cw0/src/lib.rs @@ -1,4 +1,5 @@ mod balance; +mod event; mod expiration; mod pagination; mod payment; @@ -9,4 +10,5 @@ pub use pagination::{ pub use payment::{may_pay, must_pay, nonpayable, one_coin, PaymentError}; pub use crate::balance::NativeBalance; +pub use crate::event::Event; pub use crate::expiration::{Duration, Expiration, DAY, HOUR, WEEK}; diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml new file mode 100644 index 000000000..8e81cfc23 --- /dev/null +++ b/packages/cw1155/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cw1155" +version = "0.6.0-alpha1" +authors = ["Huang Yi "] +edition = "2018" +description = "Definition and types for the CosmWasm-1155 interface" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cosmwasm-plus" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +[dependencies] +cw0 = { path = "../../packages/cw0", version = "0.6.0-alpha1" } +cosmwasm-std = { version = "0.14.0-beta1" } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } + +[dev-dependencies] +cosmwasm-schema = { version = "0.14.0-beta1" } diff --git a/packages/cw1155/README.md b/packages/cw1155/README.md new file mode 100644 index 000000000..c49402cfa --- /dev/null +++ b/packages/cw1155/README.md @@ -0,0 +1,112 @@ +# CW1155 Spec: Multiple Tokens + +CW1155 is a specification for managing multiple tokens based on CosmWasm. +The name and design is based on Ethereum's ERC1155 standard. + +The specification is split into multiple sections, a contract may only +implement some of this functionality, but must implement the base. + +Design decisions: + +- Fungible tokens and non-fungible tokens are treated equally, non-fungible tokens just have one max supply. + +- Approval is set or unset to some operator over entire set of tokens. (More nuanced control is defined in + [ERC1761](https://eips.ethereum.org/EIPS/eip-1761), do we want to merge them together?) + +- Mint and burn are mixed with transfer/send messages, otherwise, we'll have much more message types, e.g. + `Mint`/`MintToContract`/`BatchMint`/`BatchMintToContract`, etc. + + In transfer/send messges, `from`/`to` are optional, a `None` `from` means minting, a `None` `to` means burning, they +must not both be `None` at the same time. + +## Base + +### Messages + +`SendFrom{from, to, token_id, value, msg}` - This transfers some amount of tokens between two accounts. If `to` is an +address controlled by a smart contract, it must implement the `CW1155Receiver` interface, `msg` will be passed to it +along with other fields, otherwise, `msg` should be `None`. The operator should either be the `from` account or have +approval from it. + +`BatchSendFrom{from, to, batch: Vec<(token_id, value)>, msg}` - Batched version of `SendFrom` which can handle multiple +types of tokens at once. + +`Mint {to, token_id, value, msg}` - This mints some tokens to `to` account, If `to` is controlled by a smart contract, +it should implement `CW1155Receiver` interface, `msg` will be passed to it along with other fields, otherwise, `msg` +should be `None`. + +`BatchMint {to, batch: Vec<(token_id, value)>, msg}` - Batched version of `Mint`. + +`Burn {from, token_id, value}` - This burns some tokens from `from` account. + +`BatchBurn {from, batch: Vec<(token_id, value)>}` - Batched version of `Burn`. + +`ApproveAll{ operator, expires }` - Allows operator to transfer / send any token from the owner's account. If expiration +is set, then this allowance has a time/height limit. + +`RevokeAll { operator }` - Remove previously granted ApproveAll permission + +### Queries + +`Balance { owner, token_id }` - Query the balance of `owner` on perticular type of token, default to `0` when record not +exist. + +`BatchBalance { owner, token_ids }` - Query the balance of `owner` on multiple types of tokens, batched version of +`Balance`. + +`ApprovedForAll{owner, include_expired, start_after, limit}` - List all operators that can access all of the owner's +tokens. Return type is `ApprovedForAllResponse`. If `include_expired` is set, show expired owners in the results, +otherwise, ignore them. + +`ApprovedForAllItem{owner, operator}` - Query approved status `owner` granted to `operator`. Return type is +`ApprovedForAllItemResponse`. + +### Receiver + +Any contract wish to receive CW1155 tokens must implement `Cw1155ReceiveMsg` and `Cw1155BatchReceiveMsg`. + +`Cw1155ReceiveMsg { operator, from, token_id, amount, msg}` - + +`Cw1155BatchReceiveMsg { operator, from, batch, msg}` - + +### Events + +- `transfer(from, to, token_id, value)` + + `from`/`to` are optional, no `from` attribute means minting, no `to` attribute means burning, but they mustn't be +neglected at the same time. + + +## Metadata + +### Queries + +`TokenInfo{ token_id }` - Query metadata url of `token_id`. + +### Events + +`token_info(url, token_id)` + +Metadata url of `token_id` is changed, `url` should point to a json file. + +## Enumerable + +### Queries + +Pagination is acheived via `start_after` and `limit`. Limit is a request +set by the client, if unset, the contract will automatically set it to +`DefaultLimit` (suggested 10). If set, it will be used up to a `MaxLimit` +value (suggested 30). Contracts can define other `DefaultLimit` and `MaxLimit` +values without violating the CW1155 spec, and clients should not rely on +any particular values. + +If `start_after` is unset, the query returns the first results, ordered by +lexogaphically by `token_id`. If `start_after` is set, then it returns the +first `limit` tokens *after* the given one. This allows straight-forward +pagination by taking the last result returned (a `token_id`) and using it +as the `start_after` value in a future query. + +`Tokens{owner, start_after, limit}` - List all token_ids that belong to a given owner. +Return type is `TokensResponse{tokens: Vec}`. + +`AllTokens{start_after, limit}` - Requires pagination. Lists all token_ids controlled by the contract. diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs new file mode 100644 index 000000000..c4ea357f9 --- /dev/null +++ b/packages/cw1155/examples/schema.rs @@ -0,0 +1,24 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use cw1155; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(cw1155::Cw1155HandleMsg), &out_dir); + export_schema(&schema_for!(cw1155::Cw1155QueryMsg), &out_dir); + export_schema(&schema_for!(cw1155::Cw1155ReceiveMsg), &out_dir); + export_schema(&schema_for!(cw1155::Cw1155BatchReceiveMsg), &out_dir); + export_schema(&schema_for!(cw1155::BalanceResponse), &out_dir); + export_schema(&schema_for!(cw1155::BatchBalanceResponse), &out_dir); + export_schema(&schema_for!(cw1155::ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(cw1155::IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(cw1155::TokenInfoResponse), &out_dir); + export_schema(&schema_for!(cw1155::TokensResponse), &out_dir); +} diff --git a/packages/cw1155/schema/approved_for_all_response.json b/packages/cw1155/schema/approved_for_all_response.json new file mode 100644 index 000000000..5b013ee49 --- /dev/null +++ b/packages/cw1155/schema/approved_for_all_response.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + } + } + ] + }, + "HumanAddr": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/balance_response.json b/packages/cw1155/schema/balance_response.json new file mode 100644 index 000000000..efa5d6ce0 --- /dev/null +++ b/packages/cw1155/schema/balance_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/batch_balance_response.json b/packages/cw1155/schema/batch_balance_response.json new file mode 100644 index 000000000..138cc4a43 --- /dev/null +++ b/packages/cw1155/schema/batch_balance_response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Uint128" + } + } + }, + "definitions": { + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/cw1155_batch_receive_msg.json b/packages/cw1155/schema/cw1155_batch_receive_msg.json new file mode 100644 index 000000000..9756a3bd4 --- /dev/null +++ b/packages/cw1155/schema/cw1155_batch_receive_msg.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155BatchReceiveMsg", + "description": "Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a HandleMsg", + "type": "object", + "required": [ + "batch", + "msg", + "operator" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "anyOf": [ + { + "$ref": "#/definitions/HumanAddr" + }, + { + "type": "null" + } + ] + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "operator": { + "$ref": "#/definitions/HumanAddr" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/cw1155_handle_msg.json b/packages/cw1155/schema/cw1155_handle_msg.json new file mode 100644 index 000000000..27b56a372 --- /dev/null +++ b/packages/cw1155/schema/cw1155_handle_msg.json @@ -0,0 +1,380 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155HandleMsg", + "anyOf": [ + { + "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "from", + "to", + "token_id", + "value" + ], + "properties": { + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "batch", + "from", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "if `to` is not contract, `msg` should be `None`", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + } + } + } + } + }, + { + "description": "Mint is a base message to mint tokens.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "description": "BatchMint is a base message to mint multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "batch", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + } + } + } + } + }, + { + "description": "Burn is a base message to burn tokens.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "from", + "token_id", + "value" + ], + "properties": { + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_burn" + ], + "properties": { + "batch_burn": { + "type": "object", + "required": [ + "batch", + "from" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + } + } + ] + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/cw1155_query_msg.json b/packages/cw1155/schema/cw1155_query_msg.json new file mode 100644 index 000000000..6e97a5f94 --- /dev/null +++ b/packages/cw1155/schema/cw1155_query_msg.json @@ -0,0 +1,213 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155QueryMsg", + "anyOf": [ + { + "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "token_id": { + "type": "string" + } + } + } + } + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", + "type": "object", + "required": [ + "batch_balance" + ], + "properties": { + "batch_balance": { + "type": "object", + "required": [ + "owner", + "token_ids" + ], + "properties": { + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + { + "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/HumanAddr" + }, + { + "type": "null" + } + ] + } + } + } + } + }, + { + "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", + "type": "object", + "required": [ + "is_approved_for_all" + ], + "properties": { + "is_approved_for_all": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "operator": { + "$ref": "#/definitions/HumanAddr" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + } + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + } + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/cw1155_receive_msg.json b/packages/cw1155/schema/cw1155_receive_msg.json new file mode 100644 index 000000000..67ee6d6d8 --- /dev/null +++ b/packages/cw1155/schema/cw1155_receive_msg.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155ReceiveMsg", + "description": "Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", + "type": "object", + "required": [ + "amount", + "msg", + "operator", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from": { + "description": "The account that the token transfered from", + "anyOf": [ + { + "$ref": "#/definitions/HumanAddr" + }, + { + "type": "null" + } + ] + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "operator": { + "description": "The account that executed the send message", + "allOf": [ + { + "$ref": "#/definitions/HumanAddr" + } + ] + }, + "token_id": { + "type": "string" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/is_approved_for_all_response.json b/packages/cw1155/schema/is_approved_for_all_response.json new file mode 100644 index 000000000..e3af7a983 --- /dev/null +++ b/packages/cw1155/schema/is_approved_for_all_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + } +} diff --git a/packages/cw1155/schema/token_info_response.json b/packages/cw1155/schema/token_info_response.json new file mode 100644 index 000000000..a94af98e3 --- /dev/null +++ b/packages/cw1155/schema/token_info_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "Should be a url point to a json file", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/tokens_response.json b/packages/cw1155/schema/tokens_response.json new file mode 100644 index 000000000..b8e3d75b5 --- /dev/null +++ b/packages/cw1155/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs new file mode 100644 index 000000000..b6439c5b0 --- /dev/null +++ b/packages/cw1155/src/event.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{attr, Attribute, HumanAddr, Uint128}; +use cw0::Event; + +use crate::msg::TokenId; + +pub struct TransferEvent<'a> { + pub from: Option<&'a HumanAddr>, + pub to: Option<&'a HumanAddr>, + pub token_id: TokenId, + pub amount: Uint128, +} + +impl Event for TransferEvent { + fn write_attributes(&self, attributes: &mut Vec) { + attributes.extend_from_slice(&[ + attr("action", "transfer"), + attr("token_id", self.token_id), + attr("amount", self.amount), + ]); + if let Some(from) = from { + attributes.push(attr("from", from)); + } + if let Some(to) = to { + attributes.push(attr("to", to)); + } + } +} + +pub struct MetadataEvent<'a> { + pub url: &'a str, + pub token_id: TokenId, +} + +impl Event for URLEvent { + fn write_attributes(&self, attributes: &mut Vec) { + attributes.extend_from_slice(&[ + attr("action", "set_metadata"), + attr("url", self.url), + attr("token_id", self.token_id), + ]); + } +} diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs new file mode 100644 index 000000000..7adbb6e51 --- /dev/null +++ b/packages/cw1155/src/lib.rs @@ -0,0 +1,10 @@ +pub use crate::msg::{Cw1155HandleMsg, TokenId}; +pub use crate::query::{ + Approval, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, + IsApprovedForAllResponse, TokenInfoResponse, TokensResponse, +}; +pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; + +mod msg; +mod query; +mod receiver; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs new file mode 100644 index 000000000..102ebbb3a --- /dev/null +++ b/packages/cw1155/src/msg.rs @@ -0,0 +1,69 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Binary, HumanAddr, Uint128}; +use cw0::Expiration; + +pub type TokenId = String; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Cw1155HandleMsg { + /// SendFrom is a base message to move tokens, + /// if `env.sender` is the owner or has sufficient pre-approval. + SendFrom { + from: HumanAddr, + /// If `to` is not contract, `msg` should be `None` + to: HumanAddr, + token_id: TokenId, + value: Uint128, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// BatchSendFrom is a base message to move multiple types of tokens in batch, + /// if `env.sender` is the owner or has sufficient pre-approval. + BatchSendFrom { + from: HumanAddr, + /// if `to` is not contract, `msg` should be `None` + to: HumanAddr, + batch: Vec<(TokenId, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Mint is a base message to mint tokens. + Mint { + /// If `to` is not contract, `msg` should be `None` + to: HumanAddr, + token_id: TokenId, + value: Uint128, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// BatchMint is a base message to mint multiple types of tokens in batch. + BatchMint { + /// If `to` is not contract, `msg` should be `None` + to: HumanAddr, + batch: Vec<(TokenId, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Burn is a base message to burn tokens. + Burn { + from: HumanAddr, + token_id: TokenId, + value: Uint128, + }, + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BatchBurn { + from: HumanAddr, + batch: Vec<(TokenId, Uint128)>, + }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + ApproveAll { + operator: HumanAddr, + expires: Option, + }, + /// Remove previously granted ApproveAll permission + RevokeAll { operator: HumanAddr }, +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs new file mode 100644 index 000000000..379c3a4bd --- /dev/null +++ b/packages/cw1155/src/query.rs @@ -0,0 +1,99 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Uint128}; +use cw0::Expiration; + +use crate::msg::TokenId; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Cw1155QueryMsg { + /// Returns the current balance of the given address, 0 if unset. + /// Return type: BalanceResponse. + Balance { owner: HumanAddr, token_id: TokenId }, + /// Returns the current balance of the given address for a batch of tokens, 0 if unset. + /// Return type: BatchBalanceResponse. + BatchBalance { + owner: HumanAddr, + token_ids: Vec, + }, + /// List all operators that can access all of the owner's tokens. + /// Return type: ApprovedForAllResponse. + ApprovedForAll { + owner: HumanAddr, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Query approved status `owner` granted to `operator`. + /// Return type: IsApprovedForAllResponse + IsApprovedForAll { + owner: HumanAddr, + operator: HumanAddr, + }, + + /// With MetaData Extension. + /// Query metadata of token + /// Return type: TokenInfoResponse. + TokenInfo { token_id: TokenId }, + + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + /// Return type: TokensResponse. + Tokens { + owner: HumanAddr, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + /// Return type: TokensResponse. + AllTokens { + start_after: Option, + limit: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct BalanceResponse { + pub balance: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct BatchBalanceResponse { + pub balances: Vec, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Approval { + /// Account that can transfer/send the token + pub spender: HumanAddr, + /// When the Approval expires (maybe Expiration::never) + pub expires: Expiration, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ApprovedForAllResponse { + pub operators: Vec, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct IsApprovedForAllResponse { + pub approved: bool, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct TokenInfoResponse { + /// Should be a url point to a json file + pub url: String, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct TokensResponse { + /// Contains all token_ids in lexicographical ordering + /// If there are more than `limit`, use `start_from` in future queries + /// to achieve pagination. + pub tokens: Vec, +} diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs new file mode 100644 index 000000000..9fccec2fd --- /dev/null +++ b/packages/cw1155/src/receiver.rs @@ -0,0 +1,75 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_binary, Binary, CosmosMsg, HumanAddr, StdResult, Uint128, WasmMsg}; + +use crate::msg::TokenId; + +/// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Cw1155ReceiveMsg { + /// The account that executed the send message + pub operator: HumanAddr, + /// The account that the token transfered from + pub from: Option, + pub token_id: TokenId, + pub amount: Uint128, + pub msg: Binary, +} + +impl Cw1155ReceiveMsg { + /// serializes the message + pub fn into_binary(self) -> StdResult { + let msg = ReceiverHandleMsg::Receive(self); + to_binary(&msg) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg(self, contract_addr: HumanAddr) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + contract_addr, + msg, + send: vec![], + }; + Ok(execute.into()) + } +} + +/// Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a HandleMsg +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Cw1155BatchReceiveMsg { + pub operator: HumanAddr, + pub from: Option, + pub batch: Vec<(TokenId, Uint128)>, + pub msg: Binary, +} + +impl Cw1155BatchReceiveMsg { + /// serializes the message + pub fn into_binary(self) -> StdResult { + let msg = ReceiverHandleMsg::BatchReceive(self); + to_binary(&msg) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg(self, contract_addr: HumanAddr) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + contract_addr, + msg, + send: vec![], + }; + Ok(execute.into()) + } +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +enum ReceiverHandleMsg { + Receive(Cw1155ReceiveMsg), + BatchReceive(Cw1155BatchReceiveMsg), +}