diff --git a/.sqlx/query-5490d5e563b6efe31d43528f2098ba10dbf369e71332fd051d82fbae9b292347.json b/.sqlx/query-2b9b97a958cfc72138b014340569d9ebe57146a9267e5585bf8db8c947aa9790.json similarity index 59% rename from .sqlx/query-5490d5e563b6efe31d43528f2098ba10dbf369e71332fd051d82fbae9b292347.json rename to .sqlx/query-2b9b97a958cfc72138b014340569d9ebe57146a9267e5585bf8db8c947aa9790.json index 366b410b8..3af03cb8b 100644 --- a/.sqlx/query-5490d5e563b6efe31d43528f2098ba10dbf369e71332fd051d82fbae9b292347.json +++ b/.sqlx/query-2b9b97a958cfc72138b014340569d9ebe57146a9267e5585bf8db8c947aa9790.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n hash, kind, name, ticker, precision, icon_url, description,\n is_sensitive_content, is_visible, hidden_puzzle_hash\n FROM assets\n WHERE hash = ?\n ", + "query": "\n SELECT\n hash, kind, name, ticker, precision, icon_url, description,\n is_sensitive_content, is_visible, hidden_puzzle_hash,\n fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee,\n fee_allow_zero_price, fee_allow_revoke_fee_bypass\n FROM assets\n WHERE hash = ?\n ", "describe": { "columns": [ { @@ -52,6 +52,31 @@ "name": "hidden_puzzle_hash", "ordinal": 9, "type_info": "Blob" + }, + { + "name": "fee_issuer_puzzle_hash", + "ordinal": 10, + "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 11, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 12, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 13, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 14, + "type_info": "Bool" } ], "parameters": { @@ -67,8 +92,13 @@ true, false, false, + true, + true, + true, + true, + true, true ] }, - "hash": "5490d5e563b6efe31d43528f2098ba10dbf369e71332fd051d82fbae9b292347" + "hash": "2b9b97a958cfc72138b014340569d9ebe57146a9267e5585bf8db8c947aa9790" } diff --git a/.sqlx/query-e1354916f0346d1eed272947471232e4d15b99bef39202cafa6b034c3fa441e9.json b/.sqlx/query-375873006e2772b1642dbadb3caf306db998a49af0cb32168c4810e132de3f60.json similarity index 67% rename from .sqlx/query-e1354916f0346d1eed272947471232e4d15b99bef39202cafa6b034c3fa441e9.json rename to .sqlx/query-375873006e2772b1642dbadb3caf306db998a49af0cb32168c4810e132de3f60.json index 1d1167035..897fd1749 100644 --- a/.sqlx/query-e1354916f0346d1eed272947471232e4d15b99bef39202cafa6b034c3fa441e9.json +++ b/.sqlx/query-375873006e2772b1642dbadb3caf306db998a49af0cb32168c4810e132de3f60.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT \t\n height, timestamp, coin_id, puzzle_hash, parent_coin_hash, amount,\n is_created_in_block, is_spent_in_block, asset_hash, asset_description,\n asset_is_visible, asset_is_sensitive_content, asset_name, asset_icon_url,\n asset_kind, p2_puzzle_hash, asset_ticker, asset_precision, asset_hidden_puzzle_hash\n FROM transaction_coins \n WHERE height = ?", + "query": "SELECT \t\n height, timestamp, coin_id, puzzle_hash, parent_coin_hash, amount,\n is_created_in_block, is_spent_in_block, asset_hash, asset_description,\n asset_is_visible, asset_is_sensitive_content, asset_name, asset_icon_url,\n asset_kind, p2_puzzle_hash, asset_ticker, asset_precision, asset_hidden_puzzle_hash,\n assets.fee_issuer_puzzle_hash AS asset_fee_issuer_puzzle_hash,\n assets.fee_basis_points AS asset_fee_basis_points,\n assets.fee_min_fee AS asset_fee_min_fee,\n assets.fee_allow_zero_price AS asset_fee_allow_zero_price,\n assets.fee_allow_revoke_fee_bypass AS asset_fee_allow_revoke_fee_bypass\n FROM transaction_coins \n INNER JOIN assets ON assets.hash = transaction_coins.asset_hash\n WHERE height = ?", "describe": { "columns": [ { @@ -97,6 +97,31 @@ "name": "asset_hidden_puzzle_hash", "ordinal": 18, "type_info": "Blob" + }, + { + "name": "asset_fee_issuer_puzzle_hash", + "ordinal": 19, + "type_info": "Blob" + }, + { + "name": "asset_fee_basis_points", + "ordinal": 20, + "type_info": "Integer" + }, + { + "name": "asset_fee_min_fee", + "ordinal": 21, + "type_info": "Integer" + }, + { + "name": "asset_fee_allow_zero_price", + "ordinal": 22, + "type_info": "Bool" + }, + { + "name": "asset_fee_allow_revoke_fee_bypass", + "ordinal": 23, + "type_info": "Bool" } ], "parameters": { @@ -121,8 +146,13 @@ true, true, false, + true, + true, + true, + true, + true, true ] }, - "hash": "e1354916f0346d1eed272947471232e4d15b99bef39202cafa6b034c3fa441e9" + "hash": "375873006e2772b1642dbadb3caf306db998a49af0cb32168c4810e132de3f60" } diff --git a/.sqlx/query-410e6cbcdb9d0fa7d60a3ffa21c0c7990b41848de7ccfbe17a8cee2eeee60cab.json b/.sqlx/query-410e6cbcdb9d0fa7d60a3ffa21c0c7990b41848de7ccfbe17a8cee2eeee60cab.json new file mode 100644 index 000000000..23ed32577 --- /dev/null +++ b/.sqlx/query-410e6cbcdb9d0fa7d60a3ffa21c0c7990b41848de7ccfbe17a8cee2eeee60cab.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO assets (\n hash, kind, name, ticker, precision, icon_url, description,\n is_sensitive_content, is_visible, hidden_puzzle_hash,\n fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee,\n fee_allow_zero_price, fee_allow_revoke_fee_bypass\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(hash) DO UPDATE SET\n name = COALESCE(excluded.name, name),\n ticker = COALESCE(excluded.ticker, ticker),\n icon_url = COALESCE(excluded.icon_url, icon_url),\n description = COALESCE(excluded.description, description),\n is_sensitive_content = is_sensitive_content OR excluded.is_sensitive_content,\n fee_issuer_puzzle_hash = COALESCE(excluded.fee_issuer_puzzle_hash, fee_issuer_puzzle_hash),\n fee_basis_points = COALESCE(excluded.fee_basis_points, fee_basis_points),\n fee_min_fee = COALESCE(excluded.fee_min_fee, fee_min_fee),\n fee_allow_zero_price = COALESCE(excluded.fee_allow_zero_price, fee_allow_zero_price),\n fee_allow_revoke_fee_bypass = COALESCE(excluded.fee_allow_revoke_fee_bypass, fee_allow_revoke_fee_bypass)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 15 + }, + "nullable": [] + }, + "hash": "410e6cbcdb9d0fa7d60a3ffa21c0c7990b41848de7ccfbe17a8cee2eeee60cab" +} diff --git a/.sqlx/query-a101242ebd776da3f95c49b4167fd139eeeb08ff6cfcbb8a3f795c6bdcdce62c.json b/.sqlx/query-a101242ebd776da3f95c49b4167fd139eeeb08ff6cfcbb8a3f795c6bdcdce62c.json deleted file mode 100644 index ca927b1bb..000000000 --- a/.sqlx/query-a101242ebd776da3f95c49b4167fd139eeeb08ff6cfcbb8a3f795c6bdcdce62c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO assets (\n hash, kind, name, ticker, precision, icon_url, description,\n is_sensitive_content, is_visible, hidden_puzzle_hash\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(hash) DO UPDATE SET\n name = COALESCE(excluded.name, name),\n ticker = COALESCE(excluded.ticker, ticker),\n icon_url = COALESCE(excluded.icon_url, icon_url),\n description = COALESCE(excluded.description, description),\n is_sensitive_content = is_sensitive_content OR excluded.is_sensitive_content\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 10 - }, - "nullable": [] - }, - "hash": "a101242ebd776da3f95c49b4167fd139eeeb08ff6cfcbb8a3f795c6bdcdce62c" -} diff --git a/.sqlx/query-be21721e48c76c5a17cf3a9b7fc1ef957e38d86c038c775df760aea143c0d2d5.json b/.sqlx/query-a902e2d6a9aafa3bae81ffe9efddd2950ff5c7114c9096d6b6538bcf56749a78.json similarity index 56% rename from .sqlx/query-be21721e48c76c5a17cf3a9b7fc1ef957e38d86c038c775df760aea143c0d2d5.json rename to .sqlx/query-a902e2d6a9aafa3bae81ffe9efddd2950ff5c7114c9096d6b6538bcf56749a78.json index f7d57e123..c15139005 100644 --- a/.sqlx/query-be21721e48c76c5a17cf3a9b7fc1ef957e38d86c038c775df760aea143c0d2d5.json +++ b/.sqlx/query-a902e2d6a9aafa3bae81ffe9efddd2950ff5c7114c9096d6b6538bcf56749a78.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n parent_coin_hash, puzzle_hash, amount, asset_hidden_puzzle_hash,\n p2_puzzle_hash, parent_parent_coin_hash, parent_inner_puzzle_hash,\n parent_amount, asset_hash AS asset_id\n FROM wallet_coins\n INNER JOIN lineage_proofs ON lineage_proofs.coin_id = wallet_coins.coin_id\n WHERE coin_hash = ?\n ", + "query": "\n SELECT\n parent_coin_hash, puzzle_hash, amount, asset_hidden_puzzle_hash,\n p2_puzzle_hash, parent_parent_coin_hash, parent_inner_puzzle_hash,\n parent_amount,\n assets.fee_issuer_puzzle_hash, assets.fee_basis_points,\n assets.fee_min_fee, assets.fee_allow_zero_price,\n assets.fee_allow_revoke_fee_bypass\n FROM selectable_coins\n INNER JOIN lineage_proofs ON lineage_proofs.coin_id = selectable_coins.coin_id\n INNER JOIN assets ON assets.id = selectable_coins.asset_id\n WHERE asset_hash = ?\n ", "describe": { "columns": [ { @@ -44,9 +44,29 @@ "type_info": "Blob" }, { - "name": "asset_id", + "name": "fee_issuer_puzzle_hash", "ordinal": 8, "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 10, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 11, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 12, + "type_info": "Bool" } ], "parameters": { @@ -61,8 +81,12 @@ false, false, false, - false + true, + true, + true, + true, + true ] }, - "hash": "be21721e48c76c5a17cf3a9b7fc1ef957e38d86c038c775df760aea143c0d2d5" + "hash": "a902e2d6a9aafa3bae81ffe9efddd2950ff5c7114c9096d6b6538bcf56749a78" } diff --git a/.sqlx/query-340dbde9a1e789b3f2f808a4bce6ffb102489fdb80666275c1d3f5ed99d09471.json b/.sqlx/query-b7172ee0a44382182d10c37c3c45924b2ef76329f41cc4446804f2c6ab426b86.json similarity index 51% rename from .sqlx/query-340dbde9a1e789b3f2f808a4bce6ffb102489fdb80666275c1d3f5ed99d09471.json rename to .sqlx/query-b7172ee0a44382182d10c37c3c45924b2ef76329f41cc4446804f2c6ab426b86.json index f381d7721..988dc1d2f 100644 --- a/.sqlx/query-340dbde9a1e789b3f2f808a4bce6ffb102489fdb80666275c1d3f5ed99d09471.json +++ b/.sqlx/query-b7172ee0a44382182d10c37c3c45924b2ef76329f41cc4446804f2c6ab426b86.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n hash, name, icon_url, description, ticker, precision,\n is_visible, is_sensitive_content, hidden_puzzle_hash\n FROM assets\n WHERE assets.kind = 0 AND assets.id != 0\n AND EXISTS (\n SELECT 1 FROM coins\n INNER JOIN p2_puzzles ON p2_puzzles.id = coins.p2_puzzle_id\n WHERE coins.asset_id = assets.id\n )\n ORDER BY name ASC\n ", + "query": "\n SELECT\n hash, name, icon_url, description, ticker, precision,\n is_visible, is_sensitive_content, hidden_puzzle_hash,\n fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee,\n fee_allow_zero_price, fee_allow_revoke_fee_bypass\n FROM assets\n WHERE assets.kind = 0 AND assets.id != 0\n AND EXISTS (\n SELECT 1 FROM coins\n INNER JOIN p2_puzzles ON p2_puzzles.id = coins.p2_puzzle_id\n WHERE coins.asset_id = assets.id\n )\n ORDER BY name ASC\n ", "describe": { "columns": [ { @@ -47,6 +47,31 @@ "name": "hidden_puzzle_hash", "ordinal": 8, "type_info": "Blob" + }, + { + "name": "fee_issuer_puzzle_hash", + "ordinal": 9, + "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 10, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 11, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 12, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 13, + "type_info": "Bool" } ], "parameters": { @@ -61,8 +86,13 @@ false, false, false, + true, + true, + true, + true, + true, true ] }, - "hash": "340dbde9a1e789b3f2f808a4bce6ffb102489fdb80666275c1d3f5ed99d09471" + "hash": "b7172ee0a44382182d10c37c3c45924b2ef76329f41cc4446804f2c6ab426b86" } diff --git a/.sqlx/query-04f151c8aebcc35eca78a94454a32175c875052ff9d756a276727071a2566e2a.json b/.sqlx/query-b948b3efab3269735febb2fc18277b5e2340f3cc38a1b6ee01e350e0249c34c7.json similarity index 50% rename from .sqlx/query-04f151c8aebcc35eca78a94454a32175c875052ff9d756a276727071a2566e2a.json rename to .sqlx/query-b948b3efab3269735febb2fc18277b5e2340f3cc38a1b6ee01e350e0249c34c7.json index e491599fb..0838fa07f 100644 --- a/.sqlx/query-04f151c8aebcc35eca78a94454a32175c875052ff9d756a276727071a2566e2a.json +++ b/.sqlx/query-b948b3efab3269735febb2fc18277b5e2340f3cc38a1b6ee01e350e0249c34c7.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n parent_coin_hash, puzzle_hash, amount, asset_hidden_puzzle_hash,\n p2_puzzle_hash, parent_parent_coin_hash, parent_inner_puzzle_hash,\n parent_amount\n FROM selectable_coins\n INNER JOIN lineage_proofs ON lineage_proofs.coin_id = selectable_coins.coin_id\n WHERE asset_hash = ?\n ", + "query": "\n SELECT\n parent_coin_hash, puzzle_hash, amount, asset_hidden_puzzle_hash,\n p2_puzzle_hash, parent_parent_coin_hash, parent_inner_puzzle_hash,\n parent_amount, asset_hash AS asset_id,\n assets.fee_issuer_puzzle_hash, assets.fee_basis_points,\n assets.fee_min_fee, assets.fee_allow_zero_price,\n assets.fee_allow_revoke_fee_bypass\n FROM wallet_coins\n INNER JOIN lineage_proofs ON lineage_proofs.coin_id = wallet_coins.coin_id\n INNER JOIN assets ON assets.id = wallet_coins.asset_id\n WHERE coin_hash = ?\n ", "describe": { "columns": [ { @@ -42,6 +42,36 @@ "name": "parent_amount", "ordinal": 7, "type_info": "Blob" + }, + { + "name": "asset_id", + "ordinal": 8, + "type_info": "Blob" + }, + { + "name": "fee_issuer_puzzle_hash", + "ordinal": 9, + "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 10, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 11, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 12, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 13, + "type_info": "Bool" } ], "parameters": { @@ -55,8 +85,14 @@ false, false, false, - false + false, + false, + true, + true, + true, + true, + true ] }, - "hash": "04f151c8aebcc35eca78a94454a32175c875052ff9d756a276727071a2566e2a" + "hash": "b948b3efab3269735febb2fc18277b5e2340f3cc38a1b6ee01e350e0249c34c7" } diff --git a/.sqlx/query-c1446852713c869d6790b284a959740913be312fc00c22064ec0af6d490f066a.json b/.sqlx/query-c1446852713c869d6790b284a959740913be312fc00c22064ec0af6d490f066a.json deleted file mode 100644 index 128f48ffe..000000000 --- a/.sqlx/query-c1446852713c869d6790b284a959740913be312fc00c22064ec0af6d490f066a.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n strike_asset.hash AS strike_asset_hash, strike_asset.name AS strike_asset_name,\n strike_asset.ticker AS strike_asset_ticker, strike_asset.precision AS strike_asset_precision,\n strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description,\n strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content,\n strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind,\n\n underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name,\n underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision,\n underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description,\n underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content,\n underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind,\n\n p2_options.expiration_seconds AS expiration_seconds,\n strike_amount, \n underlying_coin.amount AS underlying_amount\n FROM options\n INNER JOIN assets AS option_asset ON option_asset.id = options.asset_id\n INNER JOIN coins AS underlying_coin ON underlying_coin.id = options.underlying_coin_id\n INNER JOIN p2_options ON p2_options.option_asset_id = options.asset_id\n INNER JOIN assets AS strike_asset ON strike_asset.id = options.strike_asset_id\n INNER JOIN assets AS underlying_asset ON underlying_asset.id = underlying_coin.asset_id\n WHERE option_asset.hash = ?\n ", - "describe": { - "columns": [ - { - "name": "strike_asset_hash", - "ordinal": 0, - "type_info": "Blob" - }, - { - "name": "strike_asset_name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "strike_asset_ticker", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "strike_asset_precision", - "ordinal": 3, - "type_info": "Integer" - }, - { - "name": "strike_asset_icon_url", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "strike_asset_description", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "strike_asset_is_visible", - "ordinal": 6, - "type_info": "Bool" - }, - { - "name": "strike_asset_is_sensitive_content", - "ordinal": 7, - "type_info": "Bool" - }, - { - "name": "strike_asset_hidden_puzzle_hash", - "ordinal": 8, - "type_info": "Blob" - }, - { - "name": "strike_asset_kind", - "ordinal": 9, - "type_info": "Integer" - }, - { - "name": "underlying_asset_hash", - "ordinal": 10, - "type_info": "Blob" - }, - { - "name": "underlying_asset_name", - "ordinal": 11, - "type_info": "Text" - }, - { - "name": "underlying_asset_ticker", - "ordinal": 12, - "type_info": "Text" - }, - { - "name": "underlying_asset_precision", - "ordinal": 13, - "type_info": "Integer" - }, - { - "name": "underlying_asset_icon_url", - "ordinal": 14, - "type_info": "Text" - }, - { - "name": "underlying_asset_description", - "ordinal": 15, - "type_info": "Text" - }, - { - "name": "underlying_asset_is_visible", - "ordinal": 16, - "type_info": "Bool" - }, - { - "name": "underlying_asset_is_sensitive_content", - "ordinal": 17, - "type_info": "Bool" - }, - { - "name": "underlying_asset_hidden_puzzle_hash", - "ordinal": 18, - "type_info": "Blob" - }, - { - "name": "underlying_asset_kind", - "ordinal": 19, - "type_info": "Integer" - }, - { - "name": "expiration_seconds", - "ordinal": 20, - "type_info": "Integer" - }, - { - "name": "strike_amount", - "ordinal": 21, - "type_info": "Blob" - }, - { - "name": "underlying_amount", - "ordinal": 22, - "type_info": "Blob" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - true, - true, - false, - true, - true, - false, - false, - true, - false, - false, - true, - true, - false, - true, - true, - false, - false, - true, - false, - false, - false, - false - ] - }, - "hash": "c1446852713c869d6790b284a959740913be312fc00c22064ec0af6d490f066a" -} diff --git a/.sqlx/query-d4968090a9ac9a2cd512fa3eabc6c54ab9803103799a49bb19fe46cf0ae24a6d.json b/.sqlx/query-c4faf18b76df263035aebbc7a72bd3b1505bcc88a0c3a7a1771c4080f7e5e6e0.json similarity index 55% rename from .sqlx/query-d4968090a9ac9a2cd512fa3eabc6c54ab9803103799a49bb19fe46cf0ae24a6d.json rename to .sqlx/query-c4faf18b76df263035aebbc7a72bd3b1505bcc88a0c3a7a1771c4080f7e5e6e0.json index a54a199bc..f2cb2db7d 100644 --- a/.sqlx/query-d4968090a9ac9a2cd512fa3eabc6c54ab9803103799a49bb19fe46cf0ae24a6d.json +++ b/.sqlx/query-c4faf18b76df263035aebbc7a72bd3b1505bcc88a0c3a7a1771c4080f7e5e6e0.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n hash, name, icon_url, description, ticker, precision,\n is_visible, is_sensitive_content, hidden_puzzle_hash\n FROM assets\n WHERE assets.kind = 0 AND assets.id != 0\n ORDER BY name ASC\n ", + "query": "\n SELECT\n hash, name, icon_url, description, ticker, precision,\n is_visible, is_sensitive_content, hidden_puzzle_hash,\n fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee,\n fee_allow_zero_price, fee_allow_revoke_fee_bypass\n FROM assets\n WHERE assets.kind = 0 AND assets.id != 0\n ORDER BY name ASC\n ", "describe": { "columns": [ { @@ -47,6 +47,31 @@ "name": "hidden_puzzle_hash", "ordinal": 8, "type_info": "Blob" + }, + { + "name": "fee_issuer_puzzle_hash", + "ordinal": 9, + "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 10, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 11, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 12, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 13, + "type_info": "Bool" } ], "parameters": { @@ -61,8 +86,13 @@ false, false, false, + true, + true, + true, + true, + true, true ] }, - "hash": "d4968090a9ac9a2cd512fa3eabc6c54ab9803103799a49bb19fe46cf0ae24a6d" + "hash": "c4faf18b76df263035aebbc7a72bd3b1505bcc88a0c3a7a1771c4080f7e5e6e0" } diff --git a/.sqlx/query-d37b02095321bfa821bf62ff4f7e6f5914769af5bb07d934c2d30c66aed5325a.json b/.sqlx/query-d37b02095321bfa821bf62ff4f7e6f5914769af5bb07d934c2d30c66aed5325a.json new file mode 100644 index 000000000..ea2cda528 --- /dev/null +++ b/.sqlx/query-d37b02095321bfa821bf62ff4f7e6f5914769af5bb07d934c2d30c66aed5325a.json @@ -0,0 +1,212 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n strike_asset.hash AS strike_asset_hash, strike_asset.name AS strike_asset_name,\n strike_asset.ticker AS strike_asset_ticker, strike_asset.precision AS strike_asset_precision,\n strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description,\n strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content,\n strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind,\n strike_asset.fee_issuer_puzzle_hash AS strike_fee_issuer_puzzle_hash,\n strike_asset.fee_basis_points AS strike_fee_basis_points,\n strike_asset.fee_min_fee AS strike_fee_min_fee,\n strike_asset.fee_allow_zero_price AS strike_fee_allow_zero_price,\n strike_asset.fee_allow_revoke_fee_bypass AS strike_fee_allow_revoke_fee_bypass,\n\n underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name,\n underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision,\n underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description,\n underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content,\n underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind,\n underlying_asset.fee_issuer_puzzle_hash AS underlying_fee_issuer_puzzle_hash,\n underlying_asset.fee_basis_points AS underlying_fee_basis_points,\n underlying_asset.fee_min_fee AS underlying_fee_min_fee,\n underlying_asset.fee_allow_zero_price AS underlying_fee_allow_zero_price,\n underlying_asset.fee_allow_revoke_fee_bypass AS underlying_fee_allow_revoke_fee_bypass,\n\n p2_options.expiration_seconds AS expiration_seconds,\n strike_amount, \n underlying_coin.amount AS underlying_amount\n FROM options\n INNER JOIN assets AS option_asset ON option_asset.id = options.asset_id\n INNER JOIN coins AS underlying_coin ON underlying_coin.id = options.underlying_coin_id\n INNER JOIN p2_options ON p2_options.option_asset_id = options.asset_id\n INNER JOIN assets AS strike_asset ON strike_asset.id = options.strike_asset_id\n INNER JOIN assets AS underlying_asset ON underlying_asset.id = underlying_coin.asset_id\n WHERE option_asset.hash = ?\n ", + "describe": { + "columns": [ + { + "name": "strike_asset_hash", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "strike_asset_name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "strike_asset_ticker", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "strike_asset_precision", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "strike_asset_icon_url", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "strike_asset_description", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "strike_asset_is_visible", + "ordinal": 6, + "type_info": "Bool" + }, + { + "name": "strike_asset_is_sensitive_content", + "ordinal": 7, + "type_info": "Bool" + }, + { + "name": "strike_asset_hidden_puzzle_hash", + "ordinal": 8, + "type_info": "Blob" + }, + { + "name": "strike_asset_kind", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "strike_fee_issuer_puzzle_hash", + "ordinal": 10, + "type_info": "Blob" + }, + { + "name": "strike_fee_basis_points", + "ordinal": 11, + "type_info": "Integer" + }, + { + "name": "strike_fee_min_fee", + "ordinal": 12, + "type_info": "Integer" + }, + { + "name": "strike_fee_allow_zero_price", + "ordinal": 13, + "type_info": "Bool" + }, + { + "name": "strike_fee_allow_revoke_fee_bypass", + "ordinal": 14, + "type_info": "Bool" + }, + { + "name": "underlying_asset_hash", + "ordinal": 15, + "type_info": "Blob" + }, + { + "name": "underlying_asset_name", + "ordinal": 16, + "type_info": "Text" + }, + { + "name": "underlying_asset_ticker", + "ordinal": 17, + "type_info": "Text" + }, + { + "name": "underlying_asset_precision", + "ordinal": 18, + "type_info": "Integer" + }, + { + "name": "underlying_asset_icon_url", + "ordinal": 19, + "type_info": "Text" + }, + { + "name": "underlying_asset_description", + "ordinal": 20, + "type_info": "Text" + }, + { + "name": "underlying_asset_is_visible", + "ordinal": 21, + "type_info": "Bool" + }, + { + "name": "underlying_asset_is_sensitive_content", + "ordinal": 22, + "type_info": "Bool" + }, + { + "name": "underlying_asset_hidden_puzzle_hash", + "ordinal": 23, + "type_info": "Blob" + }, + { + "name": "underlying_asset_kind", + "ordinal": 24, + "type_info": "Integer" + }, + { + "name": "underlying_fee_issuer_puzzle_hash", + "ordinal": 25, + "type_info": "Blob" + }, + { + "name": "underlying_fee_basis_points", + "ordinal": 26, + "type_info": "Integer" + }, + { + "name": "underlying_fee_min_fee", + "ordinal": 27, + "type_info": "Integer" + }, + { + "name": "underlying_fee_allow_zero_price", + "ordinal": 28, + "type_info": "Bool" + }, + { + "name": "underlying_fee_allow_revoke_fee_bypass", + "ordinal": 29, + "type_info": "Bool" + }, + { + "name": "expiration_seconds", + "ordinal": 30, + "type_info": "Integer" + }, + { + "name": "strike_amount", + "ordinal": 31, + "type_info": "Blob" + }, + { + "name": "underlying_amount", + "ordinal": 32, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true, + true, + false, + true, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + true, + true, + false, + true, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false + ] + }, + "hash": "d37b02095321bfa821bf62ff4f7e6f5914769af5bb07d934c2d30c66aed5325a" +} diff --git a/.sqlx/query-e53f79f0f7e0241ab03448dbf1423c05f8077e69ae1e73513d9576af6d06ecf0.json b/.sqlx/query-e3f34c77dab8a9258f4fcb266f359e0963a1128522fcac4ddbe3fcb737e3db38.json similarity index 64% rename from .sqlx/query-e53f79f0f7e0241ab03448dbf1423c05f8077e69ae1e73513d9576af6d06ecf0.json rename to .sqlx/query-e3f34c77dab8a9258f4fcb266f359e0963a1128522fcac4ddbe3fcb737e3db38.json index 125248fca..661f3ccd0 100644 --- a/.sqlx/query-e53f79f0f7e0241ab03448dbf1423c05f8077e69ae1e73513d9576af6d06ecf0.json +++ b/.sqlx/query-e3f34c77dab8a9258f4fcb266f359e0963a1128522fcac4ddbe3fcb737e3db38.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n offers.hash as offer_id, assets.hash as asset_id,\n amount, royalty, is_requested, \n assets.description, assets.is_sensitive_content,\n assets.is_visible, assets.icon_url, assets.name,\n assets.ticker, assets.precision, assets.kind,\n assets.hidden_puzzle_hash\n FROM offer_assets \n INNER JOIN assets ON offer_assets.asset_id = assets.id\n INNER JOIN offers ON offer_assets.offer_id = offers.id\n WHERE offers.hash = ?\n ", + "query": "\n SELECT\n offers.hash as offer_id, assets.hash as asset_id,\n amount, royalty, is_requested, \n assets.description, assets.is_sensitive_content,\n assets.is_visible, assets.icon_url, assets.name,\n assets.ticker, assets.precision, assets.kind,\n assets.hidden_puzzle_hash, assets.fee_issuer_puzzle_hash,\n assets.fee_basis_points, assets.fee_min_fee,\n assets.fee_allow_zero_price, assets.fee_allow_revoke_fee_bypass\n FROM offer_assets \n INNER JOIN assets ON offer_assets.asset_id = assets.id\n INNER JOIN offers ON offer_assets.offer_id = offers.id\n WHERE offers.hash = ?\n ", "describe": { "columns": [ { @@ -72,6 +72,31 @@ "name": "hidden_puzzle_hash", "ordinal": 13, "type_info": "Blob" + }, + { + "name": "fee_issuer_puzzle_hash", + "ordinal": 14, + "type_info": "Blob" + }, + { + "name": "fee_basis_points", + "ordinal": 15, + "type_info": "Integer" + }, + { + "name": "fee_min_fee", + "ordinal": 16, + "type_info": "Integer" + }, + { + "name": "fee_allow_zero_price", + "ordinal": 17, + "type_info": "Bool" + }, + { + "name": "fee_allow_revoke_fee_bypass", + "ordinal": 18, + "type_info": "Bool" } ], "parameters": { @@ -91,8 +116,13 @@ true, false, false, + true, + true, + true, + true, + true, true ] }, - "hash": "e53f79f0f7e0241ab03448dbf1423c05f8077e69ae1e73513d9576af6d06ecf0" + "hash": "e3f34c77dab8a9258f4fcb266f359e0963a1128522fcac4ddbe3fcb737e3db38" } diff --git a/.sqlx/query-eaac1f4cc27ce1816a76a31458514845960a89e2b3401a81f4abb1cbb561e2f2.json b/.sqlx/query-ea1629eb92bbcc01fdd3ad93509321bb6be4331632bb78f2e6c210fdc84b55a8.json similarity index 64% rename from .sqlx/query-eaac1f4cc27ce1816a76a31458514845960a89e2b3401a81f4abb1cbb561e2f2.json rename to .sqlx/query-ea1629eb92bbcc01fdd3ad93509321bb6be4331632bb78f2e6c210fdc84b55a8.json index 827fca6ce..8eea699e2 100644 --- a/.sqlx/query-eaac1f4cc27ce1816a76a31458514845960a89e2b3401a81f4abb1cbb561e2f2.json +++ b/.sqlx/query-ea1629eb92bbcc01fdd3ad93509321bb6be4331632bb78f2e6c210fdc84b55a8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n asset_hash, asset_name, asset_ticker, asset_precision, asset_icon_url,\n asset_description, asset_is_visible, asset_is_sensitive_content,\n asset_hidden_puzzle_hash, wallet_coins.created_height, wallet_coins.spent_height,\n wallet_coins.parent_coin_hash, wallet_coins.puzzle_hash, wallet_coins.amount, wallet_coins.p2_puzzle_hash,\n offer_hash AS 'offer_hash?', created_timestamp, spent_timestamp,\n clawback_expiration_seconds AS 'clawback_timestamp?',\n p2_options.expiration_seconds AS option_expiration_seconds,\n\n strike_asset.hash AS strike_asset_hash, strike_asset.name AS strike_asset_name,\n strike_asset.ticker AS strike_asset_ticker, strike_asset.precision AS strike_asset_precision,\n strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description,\n strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content,\n strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind,\n\n underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name,\n underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision,\n underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description,\n underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content,\n underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind,\n \n strike_amount, \n underlying_coin.amount AS underlying_amount,\n underlying_coin.hash AS underlying_coin_id\n FROM wallet_coins\n INNER JOIN options ON options.asset_id = wallet_coins.asset_id\n INNER JOIN p2_options ON p2_options.option_asset_id = options.asset_id\n INNER JOIN coins AS underlying_coin ON underlying_coin.id = options.underlying_coin_id\n INNER JOIN assets AS strike_asset ON strike_asset.id = options.strike_asset_id\n INNER JOIN assets AS underlying_asset ON underlying_asset.id = underlying_coin.asset_id\n WHERE asset_hash = ?\n ", + "query": "\n SELECT\n asset_hash, asset_name, asset_ticker, asset_precision, asset_icon_url,\n asset_description, asset_is_visible, asset_is_sensitive_content,\n asset_hidden_puzzle_hash, wallet_coins.created_height, wallet_coins.spent_height,\n wallet_coins.parent_coin_hash, wallet_coins.puzzle_hash, wallet_coins.amount, wallet_coins.p2_puzzle_hash,\n offer_hash AS 'offer_hash?', created_timestamp, spent_timestamp,\n clawback_expiration_seconds AS 'clawback_timestamp?',\n p2_options.expiration_seconds AS option_expiration_seconds,\n\n strike_asset.hash AS strike_asset_hash, strike_asset.name AS strike_asset_name,\n strike_asset.ticker AS strike_asset_ticker, strike_asset.precision AS strike_asset_precision,\n strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description,\n strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content,\n strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind,\n strike_asset.fee_issuer_puzzle_hash AS strike_fee_issuer_puzzle_hash,\n strike_asset.fee_basis_points AS strike_fee_basis_points,\n strike_asset.fee_min_fee AS strike_fee_min_fee,\n strike_asset.fee_allow_zero_price AS strike_fee_allow_zero_price,\n strike_asset.fee_allow_revoke_fee_bypass AS strike_fee_allow_revoke_fee_bypass,\n\n underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name,\n underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision,\n underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description,\n underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content,\n underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind,\n underlying_asset.fee_issuer_puzzle_hash AS underlying_fee_issuer_puzzle_hash,\n underlying_asset.fee_basis_points AS underlying_fee_basis_points,\n underlying_asset.fee_min_fee AS underlying_fee_min_fee,\n underlying_asset.fee_allow_zero_price AS underlying_fee_allow_zero_price,\n underlying_asset.fee_allow_revoke_fee_bypass AS underlying_fee_allow_revoke_fee_bypass,\n \n strike_amount, \n underlying_coin.amount AS underlying_amount,\n underlying_coin.hash AS underlying_coin_id\n FROM wallet_coins\n INNER JOIN options ON options.asset_id = wallet_coins.asset_id\n INNER JOIN p2_options ON p2_options.option_asset_id = options.asset_id\n INNER JOIN coins AS underlying_coin ON underlying_coin.id = options.underlying_coin_id\n INNER JOIN assets AS strike_asset ON strike_asset.id = options.strike_asset_id\n INNER JOIN assets AS underlying_asset ON underlying_asset.id = underlying_coin.asset_id\n WHERE asset_hash = ?\n ", "describe": { "columns": [ { @@ -154,68 +154,118 @@ "type_info": "Integer" }, { - "name": "underlying_asset_hash", + "name": "strike_fee_issuer_puzzle_hash", "ordinal": 30, "type_info": "Blob" }, { - "name": "underlying_asset_name", + "name": "strike_fee_basis_points", "ordinal": 31, + "type_info": "Integer" + }, + { + "name": "strike_fee_min_fee", + "ordinal": 32, + "type_info": "Integer" + }, + { + "name": "strike_fee_allow_zero_price", + "ordinal": 33, + "type_info": "Bool" + }, + { + "name": "strike_fee_allow_revoke_fee_bypass", + "ordinal": 34, + "type_info": "Bool" + }, + { + "name": "underlying_asset_hash", + "ordinal": 35, + "type_info": "Blob" + }, + { + "name": "underlying_asset_name", + "ordinal": 36, "type_info": "Text" }, { "name": "underlying_asset_ticker", - "ordinal": 32, + "ordinal": 37, "type_info": "Text" }, { "name": "underlying_asset_precision", - "ordinal": 33, + "ordinal": 38, "type_info": "Integer" }, { "name": "underlying_asset_icon_url", - "ordinal": 34, + "ordinal": 39, "type_info": "Text" }, { "name": "underlying_asset_description", - "ordinal": 35, + "ordinal": 40, "type_info": "Text" }, { "name": "underlying_asset_is_visible", - "ordinal": 36, + "ordinal": 41, "type_info": "Bool" }, { "name": "underlying_asset_is_sensitive_content", - "ordinal": 37, + "ordinal": 42, "type_info": "Bool" }, { "name": "underlying_asset_hidden_puzzle_hash", - "ordinal": 38, + "ordinal": 43, "type_info": "Blob" }, { "name": "underlying_asset_kind", - "ordinal": 39, + "ordinal": 44, "type_info": "Integer" }, + { + "name": "underlying_fee_issuer_puzzle_hash", + "ordinal": 45, + "type_info": "Blob" + }, + { + "name": "underlying_fee_basis_points", + "ordinal": 46, + "type_info": "Integer" + }, + { + "name": "underlying_fee_min_fee", + "ordinal": 47, + "type_info": "Integer" + }, + { + "name": "underlying_fee_allow_zero_price", + "ordinal": 48, + "type_info": "Bool" + }, + { + "name": "underlying_fee_allow_revoke_fee_bypass", + "ordinal": 49, + "type_info": "Bool" + }, { "name": "strike_amount", - "ordinal": 40, + "ordinal": 50, "type_info": "Blob" }, { "name": "underlying_amount", - "ordinal": 41, + "ordinal": 51, "type_info": "Blob" }, { "name": "underlying_coin_id", - "ordinal": 42, + "ordinal": 52, "type_info": "Blob" } ], @@ -253,6 +303,11 @@ false, true, false, + true, + true, + true, + true, + true, false, true, true, @@ -263,10 +318,15 @@ false, true, false, + true, + true, + true, + true, + true, false, false, false ] }, - "hash": "eaac1f4cc27ce1816a76a31458514845960a89e2b3401a81f4abb1cbb561e2f2" + "hash": "ea1629eb92bbcc01fdd3ad93509321bb6be4331632bb78f2e6c210fdc84b55a8" } diff --git a/Cargo.lock b/Cargo.lock index f4949c3fe..617296de9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1161,8 +1161,7 @@ dependencies = [ [[package]] name = "chia-sdk-client" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3857ebe69820f4644d346bf00713b2cfdb071af5c3070da6eca5f0d7a3d1184a" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "aws-lc-rs", "chia-protocol", @@ -1182,8 +1181,7 @@ dependencies = [ [[package]] name = "chia-sdk-coinset" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b893b1e6bad18ac210daddf70b53a172618810e54206617cca1d37faa7ad7bf" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "chia-protocol", "hex", @@ -1196,8 +1194,7 @@ dependencies = [ [[package]] name = "chia-sdk-derive" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddb12a31244583a1e2d14b8e379bd8829ac1fb54e8ae4d2b9de8b8aab269b2c" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "convert_case 0.8.0", "quote", @@ -1207,8 +1204,7 @@ dependencies = [ [[package]] name = "chia-sdk-driver" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbee2d08c43e0ed406a6e67ac4dca252c0d9efaf8b15d1e6019d05b5e041625a" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "bigdecimal", "bip39", @@ -1217,6 +1213,7 @@ dependencies = [ "chia-protocol", "chia-puzzle-types", "chia-puzzles", + "chia-sdk-puzzles", "chia-sdk-signer", "chia-sdk-types", "chia-sdk-utils", @@ -1238,11 +1235,18 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "chia-sdk-puzzles" +version = "0.33.0" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" +dependencies = [ + "hex-literal", +] + [[package]] name = "chia-sdk-signer" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1272d28e427aaaa4505f8b8e5504505ca425fce24882317c62b12702094aac2e" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "chia-bls 0.36.1", "chia-consensus", @@ -1260,8 +1264,7 @@ dependencies = [ [[package]] name = "chia-sdk-test" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc86094f4ee78b512a2da5ce96df4c07d2d98ba4caf2a743ee0afbca5d5fe857" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "anyhow", "bincode 2.0.1", @@ -1298,8 +1301,7 @@ dependencies = [ [[package]] name = "chia-sdk-types" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d570486f47a35663718a47b18617101f6300dcad06dc3d33afe70fb433248ea" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "chia-bls 0.36.1", "chia-consensus", @@ -1307,6 +1309,7 @@ dependencies = [ "chia-puzzle-types", "chia-puzzles", "chia-sdk-derive", + "chia-sdk-puzzles", "chia-secp", "chia-sha2 0.36.1", "chialisp", @@ -1323,8 +1326,7 @@ dependencies = [ [[package]] name = "chia-sdk-utils" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cecade7c3fe750845cde404e2c69aaeb75caf88dc343fff2ff4952554eccf9" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "bech32", "chia-protocol", @@ -1442,8 +1444,7 @@ dependencies = [ [[package]] name = "chia-wallet-sdk" version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862bece82985b64931b25bb8a3856b1807cd44deceb520ce1f56001a320956e" +source = "git+https://github.com/cameroncooper/chia-wallet-sdk.git?rev=7856417c6b22aa319cc6d024018f597f6fb55139#7856417c6b22aa319cc6d024018f597f6fb55139" dependencies = [ "chia-bls 0.36.1", "chia-consensus", diff --git a/Cargo.toml b/Cargo.toml index af545fa20..0ef038e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ tauri-specta = "2.0.0-rc.21" utoipa = "5.2.0" # Chia -chia-wallet-sdk = { version = "0.33.0", features = ["rustls", "offer-compression", "peer-simulator"] } +chia-wallet-sdk = { git = "https://github.com/cameroncooper/chia-wallet-sdk.git", rev = "7856417c6b22aa319cc6d024018f597f6fb55139", features = ["rustls", "offer-compression", "peer-simulator"] } chia_streamable_macro = "0.36.1" chia-traits = "0.36.1" chia-sha2 = "0.36.1" diff --git a/crates/sage-api/src/records/token.rs b/crates/sage-api/src/records/token.rs index ab41aa07f..07c076683 100644 --- a/crates/sage-api/src/records/token.rs +++ b/crates/sage-api/src/records/token.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::Amount; +use crate::{Amount, FeePolicy}; #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "tauri", derive(specta::Type))] @@ -16,4 +16,5 @@ pub struct TokenRecord { pub balance: Amount, pub selectable_balance: Amount, pub revocation_address: Option, + pub fee_policy: Option, } diff --git a/crates/sage-api/src/requests/offers.rs b/crates/sage-api/src/requests/offers.rs index f17b07a20..8ea45f40d 100644 --- a/crates/sage-api/src/requests/offers.rs +++ b/crates/sage-api/src/requests/offers.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - Amount, OfferRecord, OfferRecordStatus, OfferSummary, SpendBundleJson, TransactionSummary, + Amount, FeePolicy, OfferRecord, OfferRecordStatus, OfferSummary, SpendBundleJson, + TransactionSummary, }; use super::TransactionResponse; @@ -52,6 +53,10 @@ pub struct OfferAmount { #[serde(default)] #[cfg_attr(feature = "openapi", schema(nullable = true))] pub hidden_puzzle_hash: Option, + /// Optional fee policy for requested CAT assets + #[serde(default)] + #[cfg_attr(feature = "openapi", schema(nullable = true))] + pub fee_policy: Option, /// Amount of the asset pub amount: Amount, } diff --git a/crates/sage-api/src/requests/transactions.rs b/crates/sage-api/src/requests/transactions.rs index 50576f91d..a907d0eaf 100644 --- a/crates/sage-api/src/requests/transactions.rs +++ b/crates/sage-api/src/requests/transactions.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{Amount, CoinSpendJson, SpendBundleJson, TransactionSummary}; +use crate::{Amount, CoinSpendJson, FeePolicy, SpendBundleJson, TransactionSummary}; /// Send XCH to an address #[cfg_attr( @@ -212,6 +212,10 @@ pub struct IssueCat { pub amount: Amount, /// Transaction fee pub fee: Amount, + /// Optional transfer fee policy for fee CAT issuance + #[serde(default)] + #[cfg_attr(feature = "openapi", schema(nullable = true))] + pub fee_policy: Option, /// Whether to automatically submit the transaction #[serde(default)] #[cfg_attr(feature = "openapi", schema(default = false))] diff --git a/crates/sage-api/src/types/asset.rs b/crates/sage-api/src/types/asset.rs index accf323c2..3eb49be8a 100644 --- a/crates/sage-api/src/types/asset.rs +++ b/crates/sage-api/src/types/asset.rs @@ -1,5 +1,23 @@ use serde::{Deserialize, Serialize}; +use crate::Amount; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "tauri", derive(specta::Type))] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct FeePolicy { + /// Address receiving transfer fees + pub recipient: String, + /// Transfer fee in basis points (1/100 of a percent) + pub fee_basis_points: u16, + /// Minimum fee amount in mojos + pub min_fee: Amount, + /// Whether zero-price transfers can bypass fees + pub allow_zero_price: bool, + /// Whether revocations can bypass fees + pub allow_revoke_fee_bypass: bool, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "tauri", derive(specta::Type))] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] @@ -13,6 +31,7 @@ pub struct Asset { pub is_sensitive_content: bool, pub is_visible: bool, pub revocation_address: Option, + pub fee_policy: Option, pub kind: AssetKind, } diff --git a/crates/sage-database/src/tables/assets/asset.rs b/crates/sage-database/src/tables/assets/asset.rs index ae214fa34..628d96ef1 100644 --- a/crates/sage-database/src/tables/assets/asset.rs +++ b/crates/sage-database/src/tables/assets/asset.rs @@ -1,4 +1,4 @@ -use chia_wallet_sdk::prelude::*; +use chia_wallet_sdk::{driver::FeePolicy, prelude::*}; use sqlx::{SqliteExecutor, query}; use crate::{Convert, Database, DatabaseError, DatabaseTx, Result}; @@ -34,6 +34,7 @@ pub struct Asset { pub is_sensitive_content: bool, pub is_visible: bool, pub hidden_puzzle_hash: Option, + pub fee_policy: Option, pub kind: AssetKind, } @@ -174,20 +175,32 @@ async fn insert_asset(conn: impl SqliteExecutor<'_>, asset: Asset) -> Result<()> let hash = asset.hash.as_ref(); let kind = asset.kind as i64; let hidden_puzzle_hash = asset.hidden_puzzle_hash.as_deref(); + let fee_issuer_puzzle_hash = asset.fee_policy.as_ref().map(|fp| fp.issuer_fee_puzzle_hash.to_vec()); + let fee_basis_points = asset.fee_policy.as_ref().map(|fp| fp.fee_basis_points as i64); + let fee_min_fee = asset.fee_policy.as_ref().map(|fp| fp.min_fee as i64); + let fee_allow_zero_price = asset.fee_policy.as_ref().map(|fp| fp.allow_zero_price); + let fee_allow_revoke_fee_bypass = asset.fee_policy.as_ref().map(|fp| fp.allow_revoke_fee_bypass); query!( " INSERT INTO assets ( hash, kind, name, ticker, precision, icon_url, description, - is_sensitive_content, is_visible, hidden_puzzle_hash + is_sensitive_content, is_visible, hidden_puzzle_hash, + fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee, + fee_allow_zero_price, fee_allow_revoke_fee_bypass ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(hash) DO UPDATE SET name = COALESCE(excluded.name, name), ticker = COALESCE(excluded.ticker, ticker), icon_url = COALESCE(excluded.icon_url, icon_url), description = COALESCE(excluded.description, description), - is_sensitive_content = is_sensitive_content OR excluded.is_sensitive_content + is_sensitive_content = is_sensitive_content OR excluded.is_sensitive_content, + fee_issuer_puzzle_hash = COALESCE(excluded.fee_issuer_puzzle_hash, fee_issuer_puzzle_hash), + fee_basis_points = COALESCE(excluded.fee_basis_points, fee_basis_points), + fee_min_fee = COALESCE(excluded.fee_min_fee, fee_min_fee), + fee_allow_zero_price = COALESCE(excluded.fee_allow_zero_price, fee_allow_zero_price), + fee_allow_revoke_fee_bypass = COALESCE(excluded.fee_allow_revoke_fee_bypass, fee_allow_revoke_fee_bypass) ", hash, kind, @@ -199,6 +212,11 @@ async fn insert_asset(conn: impl SqliteExecutor<'_>, asset: Asset) -> Result<()> asset.is_sensitive_content, asset.is_visible, hidden_puzzle_hash, + fee_issuer_puzzle_hash, + fee_basis_points, + fee_min_fee, + fee_allow_zero_price, + fee_allow_revoke_fee_bypass, ) .execute(conn) .await?; @@ -225,6 +243,27 @@ async fn existing_hidden_puzzle_hash( .transpose() } +pub fn fee_policy_from_row( + issuer_puzzle_hash: Option>, + basis_points: Option, + min_fee: Option, + allow_zero_price: Option, + allow_revoke_fee_bypass: Option, +) -> Result> { + match (issuer_puzzle_hash, basis_points, min_fee, allow_zero_price, allow_revoke_fee_bypass) { + (Some(iph), Some(bp), Some(mf), Some(azp), Some(arfb)) => { + Ok(Some(FeePolicy::new( + iph.convert()?, + bp as u16, + mf as u64, + azp, + arfb, + ))) + } + _ => Ok(None), + } +} + async fn asset(conn: impl SqliteExecutor<'_>, hash: Bytes32) -> Result> { let hash = hash.as_ref(); @@ -232,7 +271,9 @@ async fn asset(conn: impl SqliteExecutor<'_>, hash: Bytes32) -> Result, hash: Bytes32) -> Result Result> { @@ -8,7 +8,9 @@ impl Database { " SELECT hash, name, icon_url, description, ticker, precision, - is_visible, is_sensitive_content, hidden_puzzle_hash + is_visible, is_sensitive_content, hidden_puzzle_hash, + fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee, + fee_allow_zero_price, fee_allow_revoke_fee_bypass FROM assets WHERE assets.kind = 0 AND assets.id != 0 ORDER BY name ASC @@ -28,6 +30,13 @@ impl Database { is_visible: row.is_visible, is_sensitive_content: row.is_sensitive_content, hidden_puzzle_hash: row.hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.fee_issuer_puzzle_hash, + row.fee_basis_points, + row.fee_min_fee, + row.fee_allow_zero_price, + row.fee_allow_revoke_fee_bypass, + )?, kind: AssetKind::Token, }) }) @@ -39,7 +48,9 @@ impl Database { " SELECT hash, name, icon_url, description, ticker, precision, - is_visible, is_sensitive_content, hidden_puzzle_hash + is_visible, is_sensitive_content, hidden_puzzle_hash, + fee_issuer_puzzle_hash, fee_basis_points, fee_min_fee, + fee_allow_zero_price, fee_allow_revoke_fee_bypass FROM assets WHERE assets.kind = 0 AND assets.id != 0 AND EXISTS ( @@ -64,6 +75,13 @@ impl Database { is_visible: row.is_visible, is_sensitive_content: row.is_sensitive_content, hidden_puzzle_hash: row.hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.fee_issuer_puzzle_hash, + row.fee_basis_points, + row.fee_min_fee, + row.fee_allow_zero_price, + row.fee_allow_revoke_fee_bypass, + )?, kind: AssetKind::Token, }) }) diff --git a/crates/sage-database/src/tables/assets/did.rs b/crates/sage-database/src/tables/assets/did.rs index 4380a51a3..79150870c 100644 --- a/crates/sage-database/src/tables/assets/did.rs +++ b/crates/sage-database/src/tables/assets/did.rs @@ -49,6 +49,7 @@ impl Database { is_visible: row.asset_is_visible, is_sensitive_content: row.asset_is_sensitive_content, hidden_puzzle_hash: row.asset_hidden_puzzle_hash.convert()?, + fee_policy: None, kind: AssetKind::Did, }, did_info: DidCoinInfo { diff --git a/crates/sage-database/src/tables/assets/nft.rs b/crates/sage-database/src/tables/assets/nft.rs index bd37e4a15..03848b32f 100644 --- a/crates/sage-database/src/tables/assets/nft.rs +++ b/crates/sage-database/src/tables/assets/nft.rs @@ -95,6 +95,7 @@ impl Database { is_sensitive_content: row.asset_is_sensitive_content, is_visible: row.asset_is_visible, hidden_puzzle_hash: row.asset_hidden_puzzle_hash.convert()?, + fee_policy: None, kind: AssetKind::Nft, }, nft_info: NftCoinInfo { @@ -233,6 +234,7 @@ impl Database { hidden_puzzle_hash: row .get::>, _>("asset_hidden_puzzle_hash") .convert()?, + fee_policy: None, kind: AssetKind::Nft, }, nft_info: NftCoinInfo { diff --git a/crates/sage-database/src/tables/assets/option.rs b/crates/sage-database/src/tables/assets/option.rs index 7e2d801c6..e1194c833 100644 --- a/crates/sage-database/src/tables/assets/option.rs +++ b/crates/sage-database/src/tables/assets/option.rs @@ -2,7 +2,8 @@ use chia_wallet_sdk::prelude::*; use sqlx::{Row, SqliteExecutor, query}; use crate::{ - Asset, AssetKind, CoinKind, CoinRow, Convert, Database, DatabaseTx, Result, is_valid_asset_id, + Asset, AssetKind, CoinKind, CoinRow, Convert, Database, DatabaseTx, Result, fee_policy_from_row, + is_valid_asset_id, puzzle_hash_from_address, }; @@ -89,12 +90,22 @@ impl Database { strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description, strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content, strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind, + strike_asset.fee_issuer_puzzle_hash AS strike_fee_issuer_puzzle_hash, + strike_asset.fee_basis_points AS strike_fee_basis_points, + strike_asset.fee_min_fee AS strike_fee_min_fee, + strike_asset.fee_allow_zero_price AS strike_fee_allow_zero_price, + strike_asset.fee_allow_revoke_fee_bypass AS strike_fee_allow_revoke_fee_bypass, underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name, underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision, underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description, underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content, underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind, + underlying_asset.fee_issuer_puzzle_hash AS underlying_fee_issuer_puzzle_hash, + underlying_asset.fee_basis_points AS underlying_fee_basis_points, + underlying_asset.fee_min_fee AS underlying_fee_min_fee, + underlying_asset.fee_allow_zero_price AS underlying_fee_allow_zero_price, + underlying_asset.fee_allow_revoke_fee_bypass AS underlying_fee_allow_revoke_fee_bypass, strike_amount, underlying_coin.amount AS underlying_amount, @@ -123,6 +134,7 @@ impl Database { is_visible: row.asset_is_visible, is_sensitive_content: row.asset_is_sensitive_content, hidden_puzzle_hash: row.asset_hidden_puzzle_hash.convert()?, + fee_policy: None, kind: AssetKind::Option, }, underlying_asset: Asset { @@ -135,6 +147,13 @@ impl Database { is_visible: row.underlying_asset_is_visible, is_sensitive_content: row.underlying_asset_is_sensitive_content, hidden_puzzle_hash: row.underlying_asset_hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.underlying_fee_issuer_puzzle_hash, + row.underlying_fee_basis_points, + row.underlying_fee_min_fee, + row.underlying_fee_allow_zero_price, + row.underlying_fee_allow_revoke_fee_bypass, + )?, kind: row.underlying_asset_kind.convert()?, }, underlying_amount: row.underlying_amount.convert()?, @@ -148,6 +167,13 @@ impl Database { is_visible: row.strike_asset_is_visible, is_sensitive_content: row.strike_asset_is_sensitive_content, hidden_puzzle_hash: row.strike_asset_hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.strike_fee_issuer_puzzle_hash, + row.strike_fee_basis_points, + row.strike_fee_min_fee, + row.strike_fee_allow_zero_price, + row.strike_fee_allow_revoke_fee_bypass, + )?, kind: row.strike_asset_kind.convert()?, }, strike_amount: row.strike_amount.convert()?, @@ -265,12 +291,22 @@ impl Database { strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description, strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content, strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind, + strike_asset.fee_issuer_puzzle_hash AS strike_fee_issuer_puzzle_hash, + strike_asset.fee_basis_points AS strike_fee_basis_points, + strike_asset.fee_min_fee AS strike_fee_min_fee, + strike_asset.fee_allow_zero_price AS strike_fee_allow_zero_price, + strike_asset.fee_allow_revoke_fee_bypass AS strike_fee_allow_revoke_fee_bypass, underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name, underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision, underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description, underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content, underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind, + underlying_asset.fee_issuer_puzzle_hash AS underlying_fee_issuer_puzzle_hash, + underlying_asset.fee_basis_points AS underlying_fee_basis_points, + underlying_asset.fee_min_fee AS underlying_fee_min_fee, + underlying_asset.fee_allow_zero_price AS underlying_fee_allow_zero_price, + underlying_asset.fee_allow_revoke_fee_bypass AS underlying_fee_allow_revoke_fee_bypass, p2_options.expiration_seconds AS expiration_seconds, strike_amount, @@ -299,6 +335,13 @@ impl Database { is_visible: row.underlying_asset_is_visible, is_sensitive_content: row.underlying_asset_is_sensitive_content, hidden_puzzle_hash: row.underlying_asset_hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.underlying_fee_issuer_puzzle_hash, + row.underlying_fee_basis_points, + row.underlying_fee_min_fee, + row.underlying_fee_allow_zero_price, + row.underlying_fee_allow_revoke_fee_bypass, + )?, kind: row.underlying_asset_kind.convert()?, }, underlying_amount: row.underlying_amount.convert()?, @@ -312,6 +355,13 @@ impl Database { is_visible: row.strike_asset_is_visible, is_sensitive_content: row.strike_asset_is_sensitive_content, hidden_puzzle_hash: row.strike_asset_hidden_puzzle_hash.convert()?, + fee_policy: fee_policy_from_row( + row.strike_fee_issuer_puzzle_hash, + row.strike_fee_basis_points, + row.strike_fee_min_fee, + row.strike_fee_allow_zero_price, + row.strike_fee_allow_revoke_fee_bypass, + )?, kind: row.strike_asset_kind.convert()?, }, strike_amount: row.strike_amount.convert()?, @@ -380,12 +430,22 @@ async fn owned_options( strike_asset.icon_url AS strike_asset_icon_url, strike_asset.description AS strike_asset_description, strike_asset.is_visible AS strike_asset_is_visible, strike_asset.is_sensitive_content AS strike_asset_is_sensitive_content, strike_asset.hidden_puzzle_hash AS strike_asset_hidden_puzzle_hash, strike_asset.kind AS strike_asset_kind, + strike_asset.fee_issuer_puzzle_hash AS strike_fee_issuer_puzzle_hash, + strike_asset.fee_basis_points AS strike_fee_basis_points, + strike_asset.fee_min_fee AS strike_fee_min_fee, + strike_asset.fee_allow_zero_price AS strike_fee_allow_zero_price, + strike_asset.fee_allow_revoke_fee_bypass AS strike_fee_allow_revoke_fee_bypass, underlying_asset.hash AS underlying_asset_hash, underlying_asset.name AS underlying_asset_name, underlying_asset.ticker AS underlying_asset_ticker, underlying_asset.precision AS underlying_asset_precision, underlying_asset.icon_url AS underlying_asset_icon_url, underlying_asset.description AS underlying_asset_description, underlying_asset.is_visible AS underlying_asset_is_visible, underlying_asset.is_sensitive_content AS underlying_asset_is_sensitive_content, underlying_asset.hidden_puzzle_hash AS underlying_asset_hidden_puzzle_hash, underlying_asset.kind AS underlying_asset_kind, + underlying_asset.fee_issuer_puzzle_hash AS underlying_fee_issuer_puzzle_hash, + underlying_asset.fee_basis_points AS underlying_fee_basis_points, + underlying_asset.fee_min_fee AS underlying_fee_min_fee, + underlying_asset.fee_allow_zero_price AS underlying_fee_allow_zero_price, + underlying_asset.fee_allow_revoke_fee_bypass AS underlying_fee_allow_revoke_fee_bypass, strike_amount, underlying_coin.amount AS underlying_amount, underlying_coin.hash AS underlying_coin_id, @@ -484,6 +544,7 @@ async fn owned_options( hidden_puzzle_hash: row .get::>, _>("asset_hidden_puzzle_hash") .convert()?, + fee_policy: None, kind: AssetKind::Option, }, underlying_amount: row.get::, _>("underlying_amount").convert()?, @@ -501,6 +562,13 @@ async fn owned_options( hidden_puzzle_hash: row .get::>, _>("underlying_asset_hidden_puzzle_hash") .convert()?, + fee_policy: fee_policy_from_row( + row.get::>, _>("underlying_fee_issuer_puzzle_hash"), + row.get::, _>("underlying_fee_basis_points"), + row.get::, _>("underlying_fee_min_fee"), + row.get::, _>("underlying_fee_allow_zero_price"), + row.get::, _>("underlying_fee_allow_revoke_fee_bypass"), + )?, kind: row .get::, _>("underlying_asset_kind") .map(Convert::convert) @@ -520,6 +588,13 @@ async fn owned_options( hidden_puzzle_hash: row .get::>, _>("strike_asset_hidden_puzzle_hash") .convert()?, + fee_policy: fee_policy_from_row( + row.get::>, _>("strike_fee_issuer_puzzle_hash"), + row.get::, _>("strike_fee_basis_points"), + row.get::, _>("strike_fee_min_fee"), + row.get::, _>("strike_fee_allow_zero_price"), + row.get::, _>("strike_fee_allow_revoke_fee_bypass"), + )?, kind: row .get::, _>("strike_asset_kind") .map(Convert::convert) diff --git a/crates/sage-database/src/tables/coins.rs b/crates/sage-database/src/tables/coins.rs index 128f5ec8f..507737778 100644 --- a/crates/sage-database/src/tables/coins.rs +++ b/crates/sage-database/src/tables/coins.rs @@ -6,7 +6,7 @@ use sqlx::{Row, SqliteExecutor, query}; use crate::{ AssetKind, Convert, Database, DatabaseError, DatabaseTx, Result, SerializedDid, - SerializedDidInfo, SerializedNft, SerializedNftInfo, + SerializedDidInfo, SerializedNft, SerializedNftInfo, fee_policy_from_row, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -746,9 +746,13 @@ async fn selectable_cat_coins( SELECT parent_coin_hash, puzzle_hash, amount, asset_hidden_puzzle_hash, p2_puzzle_hash, parent_parent_coin_hash, parent_inner_puzzle_hash, - parent_amount + parent_amount, + assets.fee_issuer_puzzle_hash, assets.fee_basis_points, + assets.fee_min_fee, assets.fee_allow_zero_price, + assets.fee_allow_revoke_fee_bypass FROM selectable_coins INNER JOIN lineage_proofs ON lineage_proofs.coin_id = selectable_coins.coin_id + INNER JOIN assets ON assets.id = selectable_coins.asset_id WHERE asset_hash = ? ", asset_id_ref @@ -757,6 +761,13 @@ async fn selectable_cat_coins( .await? .into_iter() .map(|row| { + let fee_policy = fee_policy_from_row( + row.fee_issuer_puzzle_hash, + row.fee_basis_points, + row.fee_min_fee, + row.fee_allow_zero_price, + row.fee_allow_revoke_fee_bypass, + )?; Ok(Cat::new( Coin::new( row.parent_coin_hash.convert()?, @@ -772,7 +783,8 @@ async fn selectable_cat_coins( asset_id, row.asset_hidden_puzzle_hash.convert()?, row.p2_puzzle_hash.convert()?, - ), + ) + .with_fee_policy(fee_policy), )) }) .collect() @@ -843,9 +855,13 @@ async fn cat_coin(conn: impl SqliteExecutor<'_>, coin_id: Bytes32) -> Result, coin_id: Bytes32) -> Result, coin_id: Bytes32) -> Result Result, _>("asset_description"), is_sensitive_content: row.get::("asset_is_sensitive_content"), is_visible: row.get::("asset_is_visible"), + fee_policy: fee_policy_from_row( + row.get::>, _>("asset_fee_issuer_puzzle_hash"), + row.get::, _>("asset_fee_basis_points"), + row.get::, _>("asset_fee_min_fee"), + row.get::, _>("asset_fee_allow_zero_price"), + row.get::, _>("asset_fee_allow_revoke_fee_bypass"), + )?, kind: row .get::, _>("asset_kind") .map(Convert::convert) @@ -76,8 +83,14 @@ async fn transaction(conn: impl SqliteExecutor<'_>, height: u32) -> Result, height: u32) -> Result Result<(Vec, Bytes32), WalletError> { + let mut ctx = SpendContext::new(); + + let p2_puzzle_hash = self.change_p2_puzzle_hash().await?; + let hint = ctx.hint(p2_puzzle_hash)?; + + let coins = self.db.selectable_xch_coins().await?; + let parent_coin = select_coins(coins, amount + fee)? + .into_iter() + .next() + .ok_or(WalletError::InsufficientFunds)?; + + let change = parent_coin.amount.saturating_sub(amount + fee); + let mut extra_conditions = Conditions::new(); + if change > 0 { + extra_conditions = extra_conditions.create_coin(p2_puzzle_hash, change, hint); + } + if fee > 0 { + extra_conditions = extra_conditions.reserve_fee(fee); + } + + let (issue_conditions, cats) = Cat::issue_fee_with_coin( + &mut ctx, + parent_coin.coin_id(), + fee_policy, + amount, + extra_conditions, + )?; + + let asset_id = cats[0].info.asset_id; + + let p2_puzzle = self.db.p2_puzzle(p2_puzzle_hash).await?; + let public_key = match p2_puzzle { + sage_database::P2Puzzle::PublicKey(pk) => pk, + _ => return Err(DriverError::MissingKey.into()), + }; + let p2 = StandardLayer::new(public_key); + p2.spend(&mut ctx, parent_coin, issue_conditions)?; + + Ok((ctx.take(), asset_id)) + } + /// Sends the given amount of CAT to the given puzzle hash. pub async fn send_cat( &self, @@ -82,6 +136,7 @@ impl Wallet { mod tests { use std::time::Duration; + use chia_wallet_sdk::driver::FeePolicy; use test_log::test; use tokio::time::sleep; @@ -310,4 +365,35 @@ mod tests { Ok(()) } + + #[test(tokio::test)] + async fn test_issue_fee_cat_persists_fee_policy() -> anyhow::Result<()> { + let mut alice = TestWallet::new(2000).await?; + + let fee_policy = FeePolicy::new(alice.puzzle_hash, 500, 1, false, true); + + let (coin_spends, asset_id) = alice + .wallet + .issue_fee_cat(1000, 0, fee_policy) + .await?; + + alice.transact(coin_spends).await?; + alice.wait_for_coins().await; + + assert_eq!(alice.wallet.db.cat_balance(asset_id).await?, 1000); + assert_eq!( + alice.wallet.db.selectable_cat_coins(asset_id).await?.len(), + 1 + ); + + let stored_fee_policy = alice + .wallet + .db + .asset(asset_id) + .await? + .and_then(|asset| asset.fee_policy); + assert_eq!(stored_fee_policy.as_ref(), Some(&fee_policy)); + + Ok(()) + } } diff --git a/crates/sage-wallet/src/wallet/offer.rs b/crates/sage-wallet/src/wallet/offer.rs index e5d8f2fa5..1d95cecc0 100644 --- a/crates/sage-wallet/src/wallet/offer.rs +++ b/crates/sage-wallet/src/wallet/offer.rs @@ -7,9 +7,42 @@ mod take_offer; pub use aggregate_offer::*; pub use make_offer::*; +use chia_wallet_sdk::{ + driver::AssetInfo, + prelude::{CatAssetInfo, CatInfo, TradePrice}, + puzzles::SETTLEMENT_PAYMENT_HASH, + types::puzzles::FeeTradePrice, +}; + +/// Converts `FeeTradePrice` values (returned by `calculate_trade_prices`) into +/// the legacy `TradePrice` format expected by the NFT `TransferNft` condition. +fn fee_trade_price_to_nft_trade_prices( + fee_trade_prices: &[FeeTradePrice], + asset_info: &AssetInfo, +) -> Vec { + fee_trade_prices + .iter() + .map(|ftp| { + let puzzle_hash = if let Some(asset_id) = ftp.asset_id { + let default = CatAssetInfo::default(); + let info = asset_info.cat(asset_id).unwrap_or(&default); + CatInfo::new(asset_id, info.hidden_puzzle_hash, SETTLEMENT_PAYMENT_HASH.into()) + .with_fee_policy(info.fee_policy) + .puzzle_hash() + .into() + } else { + SETTLEMENT_PAYMENT_HASH.into() + }; + TradePrice::new(ftp.amount, puzzle_hash) + }) + .collect() +} + #[cfg(test)] mod tests { - use chia_wallet_sdk::{chia::puzzle_types::nft::NftMetadata, prelude::*}; + use chia_wallet_sdk::{ + chia::puzzle_types::nft::NftMetadata, driver::FeePolicy, prelude::*, + }; use indexmap::indexmap; use sage_database::NftOfferInfo; use test_log::test; @@ -38,7 +71,7 @@ mod tests { ..Default::default() }, Requested { - cats: indexmap! { asset_id => RequestedCat { amount: 1000, hidden_puzzle_hash: None } }, + cats: indexmap! { asset_id => RequestedCat { amount: 1000, hidden_puzzle_hash: None, fee_policy: None } }, ..Default::default() }, None, @@ -349,7 +382,7 @@ mod tests { ..Default::default() }, Requested { - cats: indexmap! { asset_id => RequestedCat { amount: 1000, hidden_puzzle_hash: None } }, + cats: indexmap! { asset_id => RequestedCat { amount: 1000, hidden_puzzle_hash: None, fee_policy: None } }, ..Default::default() }, None, @@ -751,4 +784,56 @@ mod tests { Ok(()) } + + #[test(tokio::test)] + async fn test_offer_xch_for_fee_cat() -> anyhow::Result<()> { + let alice = TestWallet::new(1000).await?; + let mut bob = alice.next(2000).await?; + + let fee_policy = FeePolicy::new(bob.puzzle_hash, 500, 1, false, true); + + let (coin_spends, asset_id) = bob + .wallet + .issue_fee_cat(1000, 0, fee_policy) + .await?; + bob.transact(coin_spends).await?; + bob.wait_for_coins().await; + + assert_eq!(bob.wallet.db.cat_balance(asset_id).await?, 1000); + let requested_hidden_puzzle_hash = bob + .wallet + .db + .asset(asset_id) + .await? + .and_then(|asset| asset.hidden_puzzle_hash); + + let offer = alice + .wallet + .make_offer( + Offered { + xch: 750, + fee: 250, + ..Default::default() + }, + Requested { + cats: indexmap! { asset_id => RequestedCat { + amount: 1000, + hidden_puzzle_hash: requested_hidden_puzzle_hash, + fee_policy: Some(fee_policy), + } }, + ..Default::default() + }, + None, + ) + .await?; + let mut ctx = SpendContext::new(); + let parsed_offer = Offer::from_spend_bundle(&mut ctx, &offer)?; + let requested_cat = parsed_offer + .asset_info() + .cat(asset_id) + .expect("requested CAT asset info missing"); + assert_eq!(requested_cat.fee_policy.as_ref(), Some(&fee_policy)); + + Ok(()) + } } diff --git a/crates/sage-wallet/src/wallet/offer/make_offer.rs b/crates/sage-wallet/src/wallet/offer/make_offer.rs index 7f08c154c..9ea07148f 100644 --- a/crates/sage-wallet/src/wallet/offer/make_offer.rs +++ b/crates/sage-wallet/src/wallet/offer/make_offer.rs @@ -1,12 +1,14 @@ use chia_wallet_sdk::{ chia::puzzle_types::offer::{NotarizedPayment, Payment}, driver::{ - TransferNftById, calculate_royalty_payments, calculate_trade_price_amounts, + FeePolicy, TransferNftById, calculate_royalty_payments, calculate_trade_price_amounts, calculate_trade_prices, }, prelude::*, puzzles::SETTLEMENT_PAYMENT_HASH, }; + +use super::fee_trade_price_to_nft_trade_prices; use indexmap::IndexMap; use itertools::Itertools; use sage_database::{NftOfferInfo, OptionOfferInfo}; @@ -31,10 +33,11 @@ pub struct Requested { pub options: IndexMap, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, Copy)] pub struct RequestedCat { pub amount: u64, pub hidden_puzzle_hash: Option, + pub fee_policy: Option, } impl Wallet { @@ -60,7 +63,10 @@ impl Wallet { .cats .iter() .map(|(asset_id, cat)| { - asset_info.insert_cat(*asset_id, CatAssetInfo::new(cat.hidden_puzzle_hash))?; + asset_info.insert_cat( + *asset_id, + CatAssetInfo::new(cat.hidden_puzzle_hash, cat.fee_policy), + )?; Ok((*asset_id, cat.amount)) }) .collect::>()?, @@ -213,10 +219,11 @@ impl Wallet { } } - let trade_prices = calculate_trade_prices( + let fee_trade_prices = calculate_trade_prices( &calculate_trade_price_amounts(&requested_amounts, royalty_nft_count), &asset_info, ); + let nft_trade_prices = fee_trade_price_to_nft_trade_prices(&fee_trade_prices, &asset_info); for nft in spends.nfts.values().rev() { let nft = nft.last()?; @@ -233,7 +240,7 @@ impl Wallet { Some(TransferNftById::new( None, if nft.asset.info.royalty_basis_points > 0 { - trade_prices.clone() + nft_trade_prices.clone() } else { vec![] }, diff --git a/crates/sage-wallet/src/wallet/offer/offer_assets.rs b/crates/sage-wallet/src/wallet/offer/offer_assets.rs index a43d4f84d..b10e6cefe 100644 --- a/crates/sage-wallet/src/wallet/offer/offer_assets.rs +++ b/crates/sage-wallet/src/wallet/offer/offer_assets.rs @@ -10,15 +10,15 @@ use crate::{ }; impl Wallet { - pub async fn fetch_offer_cat_hidden_puzzle_hash( + pub async fn fetch_offer_cat_info( &self, asset_id: Bytes32, - ) -> Result, WalletError> { + ) -> Result, WalletError> { Ok(self .db .asset(asset_id) .await? - .and_then(|asset| asset.hidden_puzzle_hash)) + .map(|asset| CatAssetInfo::new(asset.hidden_puzzle_hash, asset.fee_policy))) } pub async fn fetch_offer_nft_info( diff --git a/crates/sage-wallet/src/wallet/offer/take_offer.rs b/crates/sage-wallet/src/wallet/offer/take_offer.rs index 1fc8e7944..e88b0d421 100644 --- a/crates/sage-wallet/src/wallet/offer/take_offer.rs +++ b/crates/sage-wallet/src/wallet/offer/take_offer.rs @@ -11,6 +11,8 @@ use sage_database::NftOfferInfo; use crate::{Wallet, WalletError}; +use super::fee_trade_price_to_nft_trade_prices; + impl Wallet { pub async fn take_offer( &self, @@ -77,14 +79,17 @@ impl Wallet { calculate_royalty_payments(&mut ctx, &offer_trade_price_amounts, &offer_royalties)?; actions.extend(royalty_payments.actions()); - // Pay requested payments + // Pay requested payments (including transfer fees for fee CATs) let mut spends = Spends::new(change_puzzle_hash); spends.add(offer.offered_coins().clone()); - actions.extend(offer.requested_payments().actions()); + actions.extend(offer.take_actions_with_transfer_fees(&mut ctx)?); // Add requested payments self.select_spends(&mut ctx, &mut spends, &actions).await?; + // Apply fee CAT trade context to relevant CAT spends + offer.apply_transfer_fee_trade_context(&mut spends)?; + // Reset DIDs and reveal trade prices let mut royalty_nft_count = 0; @@ -100,10 +105,12 @@ impl Wallet { } } - let trade_prices = calculate_trade_prices( + let fee_trade_prices = calculate_trade_prices( &calculate_trade_price_amounts(&requested_amounts, royalty_nft_count), offer.asset_info(), ); + let nft_trade_prices = + fee_trade_price_to_nft_trade_prices(&fee_trade_prices, offer.asset_info()); for nft in spends.nfts.values().rev() { let nft = nft.last()?; @@ -120,7 +127,7 @@ impl Wallet { Some(TransferNftById::new( None, if nft.asset.info.royalty_basis_points > 0 { - trade_prices.clone() + nft_trade_prices.clone() } else { vec![] }, diff --git a/crates/sage/src/endpoints/action_system.rs b/crates/sage/src/endpoints/action_system.rs index 1b0b86326..f8a66cd11 100644 --- a/crates/sage/src/endpoints/action_system.rs +++ b/crates/sage/src/endpoints/action_system.rs @@ -137,6 +137,7 @@ impl Sage { sage_api::Action::Fee(action) => { actions.push(Action::Fee(FeeAction { amount: parse_amount(action.amount)?, + reserved: true, })); } } diff --git a/crates/sage/src/endpoints/actions.rs b/crates/sage/src/endpoints/actions.rs index 3e333ade2..28e486fea 100644 --- a/crates/sage/src/endpoints/actions.rs +++ b/crates/sage/src/endpoints/actions.rs @@ -41,6 +41,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: cat.hidden_puzzle_hash, + fee_policy: None, kind: AssetKind::Token, }) .await?; diff --git a/crates/sage/src/endpoints/data.rs b/crates/sage/src/endpoints/data.rs index 26631044c..c41703bf8 100644 --- a/crates/sage/src/endpoints/data.rs +++ b/crates/sage/src/endpoints/data.rs @@ -281,6 +281,10 @@ impl Sage { .hidden_puzzle_hash .map(|puzzle_hash| Address::new(puzzle_hash, self.network().prefix()).encode()) .transpose()?, + fee_policy: cat + .fee_policy + .map(|fee_policy| self.encode_fee_policy(fee_policy)) + .transpose()?, }); } @@ -312,6 +316,10 @@ impl Sage { .hidden_puzzle_hash .map(|puzzle_hash| Address::new(puzzle_hash, self.network().prefix()).encode()) .transpose()?, + fee_policy: cat + .fee_policy + .map(|fee_policy| self.encode_fee_policy(fee_policy)) + .transpose()?, }); } @@ -350,6 +358,10 @@ impl Sage { Address::new(puzzle_hash, self.network().prefix()).encode() }) .transpose()?, + fee_policy: cat + .fee_policy + .map(|fee_policy| self.encode_fee_policy(fee_policy)) + .transpose()?, }) }) .transpose()?; diff --git a/crates/sage/src/endpoints/offers.rs b/crates/sage/src/endpoints/offers.rs index b6d720197..9567735ca 100644 --- a/crates/sage/src/endpoints/offers.rs +++ b/crates/sage/src/endpoints/offers.rs @@ -54,6 +54,7 @@ impl Sage { asset_id, amount: raw_amount, hidden_puzzle_hash: _, // We ignore this since we already have it + fee_policy: _, // Fee policy is only used for requested CATs } in req.offered_assets { let amount = parse_amount(raw_amount.clone())?; @@ -98,6 +99,7 @@ impl Sage { for OfferAmount { asset_id, hidden_puzzle_hash, + fee_policy, amount: raw_amount, } in req.requested_assets { @@ -105,20 +107,39 @@ impl Sage { if let Some(asset_id) = asset_id { if let Ok(asset_id) = parse_asset_id(asset_id.clone()) { + let existing_cat_info = wallet.fetch_offer_cat_info(asset_id).await?; + let existing_hidden_puzzle_hash = existing_cat_info + .as_ref() + .and_then(|cat| cat.hidden_puzzle_hash); + let existing_fee_policy = existing_cat_info + .as_ref() + .and_then(|cat| cat.fee_policy); + let requested_fee_policy = fee_policy + .map(|fee_policy| self.parse_fee_policy(fee_policy)) + .transpose()? + .or(existing_fee_policy); + let hidden_puzzle_hash = if let Some(hidden_puzzle_hash) = hidden_puzzle_hash { Some(parse_hash(hidden_puzzle_hash)?) } else { - wallet.fetch_offer_cat_hidden_puzzle_hash(asset_id).await? + existing_hidden_puzzle_hash }; - requested + let cat = requested .cats .entry(asset_id) .or_insert(RequestedCat { amount: 0, hidden_puzzle_hash, - }) - .amount += amount; + fee_policy: requested_fee_policy.clone(), + }); + cat.amount += amount; + if cat.hidden_puzzle_hash.is_none() { + cat.hidden_puzzle_hash = hidden_puzzle_hash; + } + if cat.fee_policy.is_none() { + cat.fee_policy = requested_fee_policy; + } } else if let Ok(nft_id) = parse_nft_id(asset_id.clone()) { if amount != 1 { return Err(Error::InvalidAmount(raw_amount.to_string())); @@ -364,12 +385,12 @@ impl Sage { } for (asset_id, amount) in requested_amounts.cats { - let hidden_puzzle_hash = offer - .asset_info() - .cat(asset_id) - .and_then(|cat| cat.hidden_puzzle_hash); + let cat_info = offer.asset_info().cat(asset_id); + let hidden_puzzle_hash = cat_info.and_then(|cat| cat.hidden_puzzle_hash); + let fee_policy = cat_info.and_then(|cat| cat.fee_policy); - self.cache_cat(asset_id, hidden_puzzle_hash).await?; + self.cache_cat(asset_id, hidden_puzzle_hash, fee_policy) + .await?; cat_rows.push(AssetToOffer { offer_id, diff --git a/crates/sage/src/endpoints/transactions.rs b/crates/sage/src/endpoints/transactions.rs index 69a78315f..4e7bdef36 100644 --- a/crates/sage/src/endpoints/transactions.rs +++ b/crates/sage/src/endpoints/transactions.rs @@ -157,8 +157,16 @@ impl Sage { let wallet = self.wallet()?; let amount = parse_amount(req.amount)?; let fee = parse_amount(req.fee)?; + let fee_policy = req + .fee_policy + .map(|fee_policy| self.parse_fee_policy(fee_policy)) + .transpose()?; - let (coin_spends, asset_id) = wallet.issue_cat(amount, fee, None).await?; + let (coin_spends, asset_id) = if let Some(fee_policy) = fee_policy.clone() { + wallet.issue_fee_cat(amount, fee, fee_policy).await? + } else { + wallet.issue_cat(amount, fee, None).await? + }; let mut tx = wallet.db.tx().await?; tx.insert_asset(Asset { @@ -171,6 +179,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: None, + fee_policy, kind: AssetKind::Token, }) .await?; @@ -268,6 +277,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: None, + fee_policy: None, kind: AssetKind::Did, }) .await?; @@ -431,7 +441,7 @@ impl Sage { let asset_id = parse_asset_id(asset_id)?; - self.cache_cat(asset_id, None).await?; + self.cache_cat(asset_id, None, None).await?; let hidden_puzzle_hash = wallet .db diff --git a/crates/sage/src/utils/cache.rs b/crates/sage/src/utils/cache.rs index 258a75faf..e28c17685 100644 --- a/crates/sage/src/utils/cache.rs +++ b/crates/sage/src/utils/cache.rs @@ -1,6 +1,6 @@ use std::{collections::hash_map::Entry, time::Duration}; -use chia_wallet_sdk::{chia::puzzle_types::nft::NftMetadata, prelude::*}; +use chia_wallet_sdk::{chia::puzzle_types::nft::NftMetadata, driver::FeePolicy, prelude::*}; use sage_assets::{DexieCat, fetch_uris_with_hash}; use sage_database::{Asset, AssetKind}; use tokio::time::timeout; @@ -12,10 +12,28 @@ impl Sage { &self, asset_id: Bytes32, hidden_puzzle_hash: Option, + fee_policy: Option, ) -> Result { let wallet = self.wallet()?; - if let Some(asset) = wallet.db.asset(asset_id).await? { + if let Some(mut asset) = wallet.db.asset(asset_id).await? { + if asset.fee_policy.is_none() && fee_policy.is_some() { + asset.fee_policy = fee_policy; + wallet.db.insert_asset(asset.clone()).await?; + } + + if asset.hidden_puzzle_hash.is_none() && hidden_puzzle_hash.is_some() { + let mut tx = wallet.db.tx().await?; + + if tx.existing_hidden_puzzle_hash(asset_id).await?.is_none() { + tx.update_hidden_puzzle_hash(asset_id, hidden_puzzle_hash) + .await?; + asset.hidden_puzzle_hash = hidden_puzzle_hash; + } + + tx.commit().await?; + } + return Ok(asset); } @@ -34,6 +52,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: asset.hidden_puzzle_hash.or(hidden_puzzle_hash), + fee_policy, kind: AssetKind::Token, } } else { @@ -47,6 +66,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash, + fee_policy, kind: AssetKind::Token, } }; @@ -118,6 +138,7 @@ impl Sage { is_sensitive_content: info.is_sensitive_content, is_visible: true, hidden_puzzle_hash: None, + fee_policy: None, kind: AssetKind::Nft, }; diff --git a/crates/sage/src/utils/confirmation.rs b/crates/sage/src/utils/confirmation.rs index 113bfb47e..0a207b309 100644 --- a/crates/sage/src/utils/confirmation.rs +++ b/crates/sage/src/utils/confirmation.rs @@ -47,7 +47,7 @@ impl Sage { CoinKind::Cat { info } => { p2_puzzle_hash = info.p2_puzzle_hash; Some( - self.cache_cat(info.asset_id, info.hidden_puzzle_hash) + self.cache_cat(info.asset_id, info.hidden_puzzle_hash, info.fee_policy) .await?, ) } @@ -71,6 +71,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: None, + fee_policy: None, kind: AssetKind::Did, })) } @@ -86,6 +87,7 @@ impl Sage { is_sensitive_content: false, is_visible: true, hidden_puzzle_hash: None, + fee_policy: None, kind: AssetKind::Option, })) } diff --git a/crates/sage/src/utils/conversions.rs b/crates/sage/src/utils/conversions.rs index a5f4ccd65..003a2104f 100644 --- a/crates/sage/src/utils/conversions.rs +++ b/crates/sage/src/utils/conversions.rs @@ -1,4 +1,5 @@ use chia_wallet_sdk::{ + driver::FeePolicy as DriverFeePolicy, driver::BURN_PUZZLE_HASH, prelude::*, puzzles::{SETTLEMENT_PAYMENT_HASH, SINGLETON_LAUNCHER_HASH}, @@ -6,7 +7,7 @@ use chia_wallet_sdk::{ use sage_api::AddressKind; use sage_database::{Asset, AssetKind}; -use crate::{Result, Sage}; +use crate::{Result, Sage, parse_amount}; impl Sage { pub fn encode_asset(&self, asset: Asset) -> Result { @@ -23,9 +24,40 @@ impl Sage { .hidden_puzzle_hash .map(|puzzle_hash| Address::new(puzzle_hash, self.network().prefix()).encode()) .transpose()?, + fee_policy: asset + .fee_policy + .map(|fee_policy| self.encode_fee_policy(fee_policy)) + .transpose()?, kind: encode_asset_kind(asset.kind), }) } + + pub fn encode_fee_policy( + &self, + fee_policy: DriverFeePolicy, + ) -> Result { + Ok(sage_api::FeePolicy { + recipient: Address::new(fee_policy.issuer_fee_puzzle_hash, self.network().prefix()) + .encode()?, + fee_basis_points: fee_policy.fee_basis_points, + min_fee: sage_api::Amount::u64(fee_policy.min_fee), + allow_zero_price: fee_policy.allow_zero_price, + allow_revoke_fee_bypass: fee_policy.allow_revoke_fee_bypass, + }) + } + + pub fn parse_fee_policy( + &self, + fee_policy: sage_api::FeePolicy, + ) -> Result { + Ok(DriverFeePolicy::new( + self.parse_address(fee_policy.recipient)?, + fee_policy.fee_basis_points, + parse_amount(fee_policy.min_fee)?, + fee_policy.allow_zero_price, + fee_policy.allow_revoke_fee_bypass, + )) + } } pub fn address_kind(p2_puzzle_hash: Option) -> AddressKind { diff --git a/crates/sage/src/utils/offer_summary.rs b/crates/sage/src/utils/offer_summary.rs index 2190fe01c..846f38942 100644 --- a/crates/sage/src/utils/offer_summary.rs +++ b/crates/sage/src/utils/offer_summary.rs @@ -48,15 +48,16 @@ impl Sage { } for (asset_id, amount) in offered_amounts.cats { - let hidden_puzzle_hash = offer - .asset_info() - .cat(asset_id) - .and_then(|cat| cat.hidden_puzzle_hash); + let cat_info = offer.asset_info().cat(asset_id); + let hidden_puzzle_hash = cat_info.and_then(|cat| cat.hidden_puzzle_hash); + let fee_policy = cat_info.and_then(|cat| cat.fee_policy); maker.push(OfferAsset { amount: Amount::u64(amount), royalty: Amount::u64(offered_royalties.cats.get(&asset_id).copied().unwrap_or(0)), - asset: self.encode_asset(self.cache_cat(asset_id, hidden_puzzle_hash).await?)?, + asset: self.encode_asset( + self.cache_cat(asset_id, hidden_puzzle_hash, fee_policy).await?, + )?, nft_royalty: None, option_assets: None, }); @@ -127,15 +128,16 @@ impl Sage { } for (asset_id, amount) in requested_amounts.cats { - let hidden_puzzle_hash = offer - .asset_info() - .cat(asset_id) - .and_then(|cat| cat.hidden_puzzle_hash); + let cat_info = offer.asset_info().cat(asset_id); + let hidden_puzzle_hash = cat_info.and_then(|cat| cat.hidden_puzzle_hash); + let fee_policy = cat_info.and_then(|cat| cat.fee_policy); taker.push(OfferAsset { amount: Amount::u64(amount), royalty: Amount::u64(*requested_royalties.cats.get(&asset_id).unwrap_or(&0)), - asset: self.encode_asset(self.cache_cat(asset_id, hidden_puzzle_hash).await?)?, + asset: self.encode_asset( + self.cache_cat(asset_id, hidden_puzzle_hash, fee_policy).await?, + )?, nft_royalty: None, option_assets: None, }); diff --git a/migrations/0006_fee_cats.sql b/migrations/0006_fee_cats.sql new file mode 100644 index 000000000..0abaab98e --- /dev/null +++ b/migrations/0006_fee_cats.sql @@ -0,0 +1,5 @@ +ALTER TABLE assets ADD COLUMN fee_issuer_puzzle_hash BLOB; +ALTER TABLE assets ADD COLUMN fee_basis_points INTEGER; +ALTER TABLE assets ADD COLUMN fee_min_fee INTEGER; +ALTER TABLE assets ADD COLUMN fee_allow_zero_price BOOLEAN; +ALTER TABLE assets ADD COLUMN fee_allow_revoke_fee_bypass BOOLEAN; diff --git a/src/bindings.ts b/src/bindings.ts index 67f25c5ed..cc268566a 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -411,7 +411,7 @@ export type AddPeer = { ip: string } export type AddressKind = "own" | "burn" | "launcher" | "offer" | "external" | "unknown" export type Amount = string | number -export type Asset = { asset_id: string | null; name: string | null; ticker: string | null; precision: number; icon_url: string | null; description: string | null; is_sensitive_content: boolean; is_visible: boolean; revocation_address: string | null; kind: AssetKind } +export type Asset = { asset_id: string | null; name: string | null; ticker: string | null; precision: number; icon_url: string | null; description: string | null; is_sensitive_content: boolean; is_visible: boolean; revocation_address: string | null; fee_policy: FeePolicy | null; kind: AssetKind } /** * Type of asset coin */ @@ -826,6 +826,27 @@ export type FeeAction = { * The fee amount, in mojos */ amount: Amount } +export type FeePolicy = { +/** + * Address receiving transfer fees + */ +recipient: string; +/** + * Transfer fee in basis points (1/100 of a percent) + */ +fee_basis_points: number; +/** + * Minimum fee amount in mojos + */ +min_fee: Amount; +/** + * Whether zero-price transfers can bypass fees + */ +allow_zero_price: boolean; +/** + * Whether revocations can bypass fees + */ +allow_revoke_fee_bypass: boolean } /** * Filter unlocked coins from a list */ @@ -1730,6 +1751,10 @@ amount: Amount; * Transaction fee */ fee: Amount; +/** + * Optional transfer fee policy for fee CAT issuance + */ +fee_policy?: FeePolicy | null; /** * Whether to automatically submit the transaction */ @@ -2002,6 +2027,10 @@ asset_id?: string | null; * Optional hidden puzzle hash for privacy */ hidden_puzzle_hash?: string | null; +/** + * Optional fee policy for requested CAT assets + */ +fee_policy?: FeePolicy | null; /** * Amount of the asset */ @@ -2524,7 +2553,7 @@ spend_bundle: SpendBundleJson; * Transaction ID */ transaction_id: string } -export type TokenRecord = { asset_id: string | null; name: string | null; ticker: string | null; precision: number; description: string | null; icon_url: string | null; visible: boolean; balance: Amount; selectable_balance: Amount; revocation_address: string | null } +export type TokenRecord = { asset_id: string | null; name: string | null; ticker: string | null; precision: number; description: string | null; icon_url: string | null; visible: boolean; balance: Amount; selectable_balance: Amount; revocation_address: string | null; fee_policy: FeePolicy | null } export type TransactionCoinRecord = { coin_id: string; amount: Amount; address: string | null; address_kind: AddressKind; asset: Asset } export type TransactionInput = { coin_id: string; amount: Amount; address: string; asset: Asset | null; outputs: TransactionOutput[] } export type TransactionOutput = { coin_id: string; amount: Amount; address: string; receiving: boolean; burning: boolean } diff --git a/src/components/Assets.tsx b/src/components/Assets.tsx index c5d8db850..ab2ac9a88 100644 --- a/src/components/Assets.tsx +++ b/src/components/Assets.tsx @@ -113,6 +113,43 @@ export function Assets({ assets, catPresence = {} }: AssetsProps) { )} + {asset.fee_policy && ( + <> + +
+
+ Fee recipient: {asset.fee_policy.recipient} +
+
+ Fee: {asset.fee_policy.fee_basis_points} bps +
+
+ Minimum fee:{' '} + {' '} + {asset.ticker ?? 'CAT'} +
+
+ {asset.fee_policy.allow_zero_price ? ( + Zero-price transfers can bypass fees + ) : ( + Zero-price transfers are fee-enforced + )} +
+
+ {asset.fee_policy.allow_revoke_fee_bypass ? ( + Revocations can bypass fees + ) : ( + Revocations are fee-enforced + )} +
+
+ + )} + {!nft_royalty && BigNumber(royalty).isGreaterThan(0) && (
Amount includes{' '} diff --git a/src/components/OfferSummaryCard.tsx b/src/components/OfferSummaryCard.tsx index 999efd2ab..e20c17cb2 100644 --- a/src/components/OfferSummaryCard.tsx +++ b/src/components/OfferSummaryCard.tsx @@ -4,6 +4,7 @@ import { formatTimestamp, fromMojos, getOfferStatus } from '@/lib/utils'; import { t } from '@lingui/core/macro'; import BigNumber from 'bignumber.js'; import { AssetIcon } from './AssetIcon'; +import { Badge } from './ui/badge'; export interface OfferSummaryCardProps { record: OfferRecord; @@ -61,7 +62,7 @@ function AssetPreview({ label, assets }: AssetPreviewProps) { {assets.map(({ amount, royalty, asset }) => (
-
+
{asset.kind !== 'nft' && asset.kind !== 'option' && ( )}{' '} {asset.name ?? asset.ticker ?? t`Unknown`} + {asset.fee_policy && ( + + {t`Fee CAT`} + + )}
))} diff --git a/src/components/TokenCard.tsx b/src/components/TokenCard.tsx index ca530b8d6..6fe89b383 100644 --- a/src/components/TokenCard.tsx +++ b/src/components/TokenCard.tsx @@ -213,6 +213,45 @@ export function TokenCard({ )} + + {asset.fee_policy && ( + + + Fee Policy + + +

+ Recipient: {asset.fee_policy.recipient} +

+

+ Fee: {asset.fee_policy.fee_basis_points} bps +

+

+ Minimum fee:{' '} + {' '} + {asset.ticker ?? 'CAT'} +

+

+ {asset.fee_policy.allow_zero_price ? ( + Zero-price transfers can bypass fees + ) : ( + Zero-price transfers are fee-enforced + )} +

+

+ {asset.fee_policy.allow_revoke_fee_bypass ? ( + Revocations can bypass fees + ) : ( + Revocations are fee-enforced + )} +

+
+
+ )} diff --git a/src/components/TokenColumns.tsx b/src/components/TokenColumns.tsx index f95e7abce..e112d7cf8 100644 --- a/src/components/TokenColumns.tsx +++ b/src/components/TokenColumns.tsx @@ -25,6 +25,7 @@ import { Link } from 'react-router-dom'; import { toast } from 'react-toastify'; import { AssetIcon } from './AssetIcon'; import { NumberFormat } from './NumberFormat'; +import { Badge } from './ui/badge'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; export interface TokenActionHandlers { @@ -67,7 +68,7 @@ export const columns = (
{name} @@ -85,6 +86,23 @@ export const columns = ( )} + + {row.original.fee_policy && ( + + + + Fee CAT + + + +

+ + Fee policy: {row.original.fee_policy.fee_basis_points} bps + +

+
+
+ )}
); }, diff --git a/src/components/TokenGridView.tsx b/src/components/TokenGridView.tsx index 95e832c81..c38443375 100644 --- a/src/components/TokenGridView.tsx +++ b/src/components/TokenGridView.tsx @@ -1,5 +1,6 @@ import { NumberFormat } from '@/components/NumberFormat'; import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { DropdownMenu, @@ -139,6 +140,13 @@ export function TokenGridView({ tokens, actionHandlers }: TokenGridViewProps) { />{' '} {token.ticker ?? ''}
+ {token.fee_policy && ( +
+ + Fee CAT + +
+ )}
diff --git a/src/components/confirmations/TokenConfirmation.tsx b/src/components/confirmations/TokenConfirmation.tsx index 07373cb20..b372eb9cd 100644 --- a/src/components/confirmations/TokenConfirmation.tsx +++ b/src/components/confirmations/TokenConfirmation.tsx @@ -26,6 +26,13 @@ interface TokenConfirmationProps { name?: string; amount?: string; currentMemo?: string; + feePolicy?: { + recipient: string; + feeBasisPoints: string; + minFee: string; + allowZeroPrice: boolean; + allowRevokeFeeBypass: boolean; + }; } export function TokenConfirmation({ @@ -37,6 +44,7 @@ export function TokenConfirmation({ name, amount, currentMemo, + feePolicy, }: TokenConfirmationProps) { const config = { split: { @@ -164,6 +172,37 @@ export function TokenConfirmation({ {ticker}
+ + {feePolicy && ( +
+
+ Fee Policy +
+
+ Recipient: {feePolicy.recipient} +
+
+ Fee: {feePolicy.feeBasisPoints} bps +
+
+ Minimum fee: {feePolicy.minFee} {ticker} +
+
+ {feePolicy.allowZeroPrice ? ( + Zero-price transfers can bypass fees + ) : ( + Zero-price transfers are fee-enforced + )} +
+
+ {feePolicy.allowRevokeFeeBypass ? ( + Revocations can bypass fees + ) : ( + Revocations are fee-enforced + )} +
+
+ )} )} diff --git a/src/components/dialogs/MakeOfferConfirmationDialog.tsx b/src/components/dialogs/MakeOfferConfirmationDialog.tsx index 368f50d70..638e8cd6e 100644 --- a/src/components/dialogs/MakeOfferConfirmationDialog.tsx +++ b/src/components/dialogs/MakeOfferConfirmationDialog.tsx @@ -75,7 +75,7 @@ function AssetDisplay({ try { const tokensWithNamesPromises = assets.tokens.map( - async ({ asset_id: assetId, amount }) => { + async ({ asset_id: assetId, amount, fee_policy }) => { try { const tokenResponse = await commands.getToken({ asset_id: assetId, @@ -92,6 +92,7 @@ function AssetDisplay({ ), iconUrl: token.icon_url, precision: token.precision, + fee_policy, }; } else { return { @@ -100,6 +101,7 @@ function AssetDisplay({ displayName: getAssetDisplayName(null, null, 'token'), iconUrl: null, precision: 3, + fee_policy, }; } } catch (error) { @@ -113,6 +115,7 @@ function AssetDisplay({ displayName: getAssetDisplayName(null, null, 'token'), iconUrl: null, precision: 3, + fee_policy, }; } }, @@ -217,17 +220,26 @@ function AssetDisplay({ }} size='sm' /> - - {' '} - {token.asset_id - ? token.displayName || - `${token.asset_id.slice(0, 8)}...` - : t`Chia`} - +
+ + {' '} + {token.asset_id + ? token.displayName || + `${token.asset_id.slice(0, 8)}...` + : t`Chia`} + + {token.fee_policy && ( + + + Fee policy ({token.fee_policy.fee_basis_points} bps) + + + )} +
))} diff --git a/src/components/selectors/AssetSelector.tsx b/src/components/selectors/AssetSelector.tsx index a99192fe9..5d5abb69a 100644 --- a/src/components/selectors/AssetSelector.tsx +++ b/src/components/selectors/AssetSelector.tsx @@ -4,7 +4,7 @@ import { TokenSelector } from '@/components/selectors/TokenSelector'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { TokenAmountInput } from '@/components/ui/masked-input'; +import { IntegerInput, TokenAmountInput } from '@/components/ui/masked-input'; import { Switch } from '@/components/ui/switch'; import { Tooltip, @@ -56,6 +56,14 @@ export function AssetSelector({ const [optionIds, setOptionIds] = useState([]); const [assetsInOffers, setAssetsInOffers] = useState>(new Set()); + const defaultFeePolicy = () => ({ + recipient: '', + fee_basis_points: '0', + min_fee: '0', + allow_zero_price: false, + allow_revoke_fee_bypass: false, + }); + useEffect(() => { if (!offering) return; Promise.all([commands.getCats({}), commands.getToken({ asset_id: null })]) @@ -108,7 +116,7 @@ export function AssetSelector({ setTokenIds([...tokenIds, newId]); setAssets({ ...assets, - tokens: [...assets.tokens, { asset_id: '', amount: '' }], + tokens: [...assets.tokens, { asset_id: '', amount: '', fee_policy: null }], }); }; @@ -147,6 +155,29 @@ export function AssetSelector({ setAssets({ ...assets, tokens: newTokens }); }; + const updateTokenFeePolicy = ( + index: number, + feePolicy: NonNullable, + ) => { + const newTokens = [...assets.tokens]; + newTokens[index] = { + ...newTokens[index], + fee_policy: feePolicy, + }; + setAssets({ ...assets, tokens: newTokens }); + }; + + const setTokenFeePolicyEnabled = (index: number, enabled: boolean) => { + const newTokens = [...assets.tokens]; + newTokens[index] = { + ...newTokens[index], + fee_policy: enabled + ? (newTokens[index].fee_policy ?? defaultFeePolicy()) + : null, + }; + setAssets({ ...assets, tokens: newTokens }); + }; + const updateNft = (index: number, value: string | null) => { const newNfts = [...assets.nfts]; newNfts[index] = value || ''; @@ -228,73 +259,164 @@ export function AssetSelector({