diff --git a/Cargo.lock b/Cargo.lock index 63b5af2c..351d5df9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,6 +270,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.0", +] + [[package]] name = "base64" version = "0.13.1" @@ -306,6 +316,12 @@ version = "0.10.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "beef" version = "0.5.2" @@ -379,14 +395,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" dependencies = [ "bech32 0.10.0-beta", - "bitcoin-internals", + "bitcoin-internals 0.2.0", "bitcoin_hashes 0.13.0", - "hex-conservative", + "hex-conservative 0.1.2", "hex_lit", "secp256k1 0.28.2", "serde", ] +[[package]] +name = "bitcoin" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" +dependencies = [ + "base58ck", + "bech32 0.11.0", + "bitcoin-internals 0.3.0", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.0", + "hex-conservative 0.2.1", + "hex_lit", + "secp256k1 0.29.1", +] + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -396,6 +429,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals 0.3.0", +] + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -444,7 +498,7 @@ dependencies = [ "minicbor", "minicbor-derive", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "ordinals", "proptest", "rand 0.7.3", @@ -477,11 +531,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ - "bitcoin-internals", - "hex-conservative", + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", +] + [[package]] name = "bitcoin_mock" version = "0.1.0" @@ -491,7 +555,7 @@ dependencies = [ "ic-btc-interface 0.1.0", "ic-cdk 0.12.2", "ic-cdk-macros 0.8.4", - "omnity_types", + "omnity_types 0.1.0", "rand 0.8.5", "serde", "serde_bytes", @@ -525,7 +589,7 @@ dependencies = [ "itertools 0.12.1", "k256 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_bytes", "serde_derive", @@ -635,7 +699,7 @@ dependencies = [ "minicbor", "minicbor-derive", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "rand 0.8.5", "ripemd", "rust_decimal", @@ -681,7 +745,7 @@ dependencies = [ "minicbor", "minicbor-derive", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "rand 0.8.5", "ripemd", "rust_decimal", @@ -780,9 +844,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.7" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818394610ed32d9e4c81025f97c8580698b69542527efde18514cf9ad1f8f5f0" +checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" dependencies = [ "anyhow", "binread", @@ -1050,7 +1114,7 @@ dependencies = [ "icrc-ledger-types", "itertools 0.13.0", "log", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_bytes", "serde_json", @@ -1406,6 +1470,56 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "doge_customs" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bech32 0.9.1", + "bincode", + "bitcoin 0.32.2", + "bitcoin-io", + "bs58", + "candid", + "ciborium", + "futures", + "getrandom 0.2.15", + "hex-conservative 0.2.1", + "ic-base-types 0.9.0 (git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01)", + "ic-canister-log", + "ic-canisters-http-types 0.9.0 (git+https://github.com/dfinity/ic?tag=release-2024-03-06_23-01%2Bp2p)", + "ic-cdk 0.12.2", + "ic-cdk-macros 0.8.4", + "ic-cdk-timers 0.6.0", + "ic-crypto-extended-bip32", + "ic-crypto-getrandom-for-wasm 0.9.0", + "ic-crypto-sha2 0.9.0 (git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01)", + "ic-ic00-types", + "ic-icrc1", + "ic-metrics-encoder", + "ic-stable-structures 0.6.5", + "ic-utils-ensure", + "ic0 0.18.11", + "k256 0.13.3 (git+https://github.com/altkdf/elliptic-curves?branch=schnorr_canister)", + "lazy_static", + "log", + "minicbor", + "minicbor-derive", + "num-traits", + "omnity_types 0.1.0 (git+https://github.com/octopus-network/omnity-interoperability.git?rev=9e851c7a1a37a997fb9ee09865fb2e54fd4e79ed)", + "rand 0.8.5", + "ripemd", + "rust_decimal", + "rust_decimal_macros", + "scopeguard", + "serde", + "serde_bytes", + "serde_json", + "serde_with 3.7.0", + "thiserror", +] + [[package]] name = "dunce" version = "1.0.5" @@ -2010,7 +2124,7 @@ dependencies = [ "icrc-ledger-types", "itertools 0.13.0", "log", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_bytes", "serde_json", @@ -2203,6 +2317,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.4", +] + [[package]] name = "hex-literal" version = "0.4.1" @@ -2265,7 +2388,7 @@ dependencies = [ "candid", "ic-cdk 0.12.2", "ic-cdk-macros 0.8.4", - "omnity_types", + "omnity_types 0.1.0", "serde", ] @@ -3365,7 +3488,7 @@ dependencies = [ "icrc-ledger-types", "log", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_json", ] @@ -3379,7 +3502,7 @@ dependencies = [ "ic-btc-interface 0.1.0", "ic-cdk 0.12.2", "ic-cdk-macros 0.8.4", - "omnity_types", + "omnity_types 0.1.0", "rand 0.8.5", "serde", "serde_bytes", @@ -3414,7 +3537,7 @@ dependencies = [ "icrc-ledger-types", "log", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_json", "thiserror", @@ -4075,7 +4198,7 @@ dependencies = [ "ic-stable-structures 0.6.5", "ic-test-utilities-load-wasm 0.9.0 (git+https://github.com/dfinity/ic?tag=release-2024-03-06_23-01%2Bp2p)", "lazy_static", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_bytes", "serde_json", @@ -4117,6 +4240,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "omnity_types" +version = "0.1.0" +source = "git+https://github.com/octopus-network/omnity-interoperability.git?rev=9e851c7a1a37a997fb9ee09865fb2e54fd4e79ed#9e851c7a1a37a997fb9ee09865fb2e54fd4e79ed" +dependencies = [ + "candid", + "ciborium", + "derive_more 0.99.17", + "env_filter", + "getrandom 0.2.15", + "hex", + "humantime", + "ic-canister-log", + "ic-canisters-http-types 0.9.0 (git+https://github.com/dfinity/ic?tag=release-2024-03-06_23-01%2Bp2p)", + "ic-cdk 0.12.2", + "ic-stable-structures 0.6.5", + "k256 0.12.0", + "lazy_static", + "log", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_derive", + "serde_json", + "serde_with 3.7.0", + "sha2", + "thiserror", + "time", +] + [[package]] name = "on_wire" version = "0.9.0" @@ -5291,6 +5444,16 @@ dependencies = [ "serde", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.14.0", + "secp256k1-sys 0.10.1", +] + [[package]] name = "secp256k1-sys" version = "0.8.1" @@ -5309,6 +5472,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "1.0.22" @@ -6054,7 +6226,7 @@ dependencies = [ "log", "num-bigint", "num-traits", - "omnity_types", + "omnity_types 0.1.0", "serde", "serde_bytes", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index 6266dc04..e897d3a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "customs/bitcoin_runes", "customs/bitcoin_brc20", "customs/icp", + "customs/doge", "route/icp", "route/bitfinity", "route/ton", @@ -14,7 +15,7 @@ members = [ "types", "mock/bitcoin", "mock/icp", - "mock/hub", + "mock/hub" ] exclude = ["route/solana"] resolver = "2" diff --git a/assets/doge_customs/service.did.d.ts b/assets/doge_customs/service.did.d.ts new file mode 100644 index 00000000..cafff3bb --- /dev/null +++ b/assets/doge_customs/service.did.d.ts @@ -0,0 +1,185 @@ +import type { Principal } from '@dfinity/principal'; +import type { ActorMethod } from '@dfinity/agent'; +import type { IDL } from '@dfinity/candid'; + +export interface Chain { + 'fee_token' : [] | [string], + 'canister_id' : string, + 'chain_id' : string, + 'counterparties' : [] | [Array], + 'chain_state' : ChainState, + 'chain_type' : ChainType, + 'contract_address' : [] | [string], +} +export type ChainState = { 'Active' : null } | + { 'Deactive' : null }; +export type ChainType = { 'SettlementChain' : null } | + { 'ExecutionChain' : null }; +export type CustomsError = { 'SendTicketErr' : string } | + { 'RpcError' : string } | + { 'TemporarilyUnavailable' : string } | + { 'HttpOutCallError' : [string, string, string] } | + { 'AlreadyProcessed' : null } | + { 'HttpStatusError' : [bigint, string, string] } | + { 'OrdTxError' : string } | + { 'NotBridgeTx' : null } | + { 'AmountIsZero' : null } | + { 'InvalidRuneId' : string } | + { 'InvalidArgs' : string } | + { 'AlreadySubmitted' : null } | + { 'InvalidTxId' : null } | + { 'NotPayFees' : null } | + { 'CallError' : [Principal, string, string] } | + { 'TxNotFoundInMemPool' : null } | + { 'Unknown' : null } | + { 'InvalidTxReceiver' : null } | + { 'UnsupportedChainId' : string } | + { 'ECDSAPublicKeyNotFound' : null } | + { 'HttpOutExceedRetryLimit' : null } | + { 'DepositUtxoNotFound' : [string, Destination] } | + { 'UnsupportedToken' : string } | + { 'CustomError' : string }; +export interface Destination { + 'token' : [] | [string], + 'target_chain_id' : string, + 'receiver' : string, +} +export interface EcdsaPublicKeyResponse { + 'public_key' : Uint8Array | number[], + 'chain_code' : Uint8Array | number[], +} +export interface GenerateTicketArgs { + 'token_id' : string, + 'target_chain_id' : string, + 'receiver' : string, +} +export interface GenerateTicketWithTxidArgs { + 'token_id' : string, + 'txid' : string, + 'target_chain_id' : string, + 'receiver' : string, +} +export interface InitArgs { + 'fee_token' : string, + 'hub_principal' : Principal, + 'chain_id' : string, + 'default_doge_rpc_config' : RpcConfig, + 'admins' : Array, +} +export interface LockTicketRequest { + 'received_at' : bigint, + 'transaction_hex' : string, + 'token_id' : string, + 'txid' : Uint8Array | number[], + 'target_chain_id' : string, + 'amount' : string, + 'receiver' : string, +} +export interface MultiRpcConfig { + 'rpc_list' : Array, + 'minimum_response_count' : number, +} +export type ReleaseTokenStatus = { 'Signing' : null } | + { 'Confirmed' : string } | + { 'Sending' : string } | + { 'Unknown' : null } | + { 'Submitted' : string } | + { 'Pending' : null }; +export type Result = { 'Ok' : Array } | + { 'Err' : CustomsError }; +export type Result_1 = { 'Ok' : null } | + { 'Err' : CustomsError }; +export type Result_2 = { 'Ok' : string } | + { 'Err' : CustomsError }; +export type Result_3 = { 'Ok' : string } | + { 'Err' : string }; +export type Result_4 = { 'Ok' : bigint } | + { 'Err' : CustomsError }; +export interface RpcConfig { 'url' : string, 'api_key' : [] | [string] } +export interface SendTicketResult { + 'txid' : Uint8Array | number[], + 'success' : boolean, + 'time_at' : bigint, +} +export interface StateProfile { + 'next_consume_ticket_seq' : bigint, + 'fee_token' : string, + 'hub_principal' : Principal, + 'ecdsa_key_name' : string, + 'doge_chain' : number, + 'next_directive_seq' : bigint, + 'doge_fee_rate' : [] | [bigint], + 'deposited_utxo' : Array<[Utxo, Destination]>, + 'fee_collector' : string, + 'ecdsa_public_key' : [] | [EcdsaPublicKeyResponse], + 'chain_id' : string, + 'pending_lock_ticket_requests' : Array<[string, LockTicketRequest]>, + 'tokens' : Array<[string, Token]>, + 'admins' : Array, + 'target_chain_factor' : Array<[string, bigint]>, + 'multi_rpc_config' : MultiRpcConfig, + 'counterparties' : Array<[string, Chain]>, + 'min_deposit_amount' : bigint, + 'next_ticket_seq' : bigint, + 'chain_state' : ChainState, + 'min_confirmations' : number, + 'tatum_rpc_config' : RpcConfig, + 'fee_payment_utxo' : Array, + 'flight_unlock_ticket_map' : Array<[bigint, SendTicketResult]>, + 'fee_token_factor' : [] | [bigint], +} +export interface Token { + 'decimals' : number, + 'token_id' : string, + 'metadata' : Array<[string, string]>, + 'icon' : [] | [string], + 'name' : string, + 'symbol' : string, +} +export interface TokenResp { + 'decimals' : number, + 'token_id' : string, + 'icon' : [] | [string], + 'symbol' : string, +} +export interface Utxo { + 'value' : bigint, + 'txid' : Uint8Array | number[], + 'vout' : number, +} +export interface _SERVICE { + 'generate_ticket' : ActorMethod<[GenerateTicketArgs], Result>, + 'generate_ticket_by_txid' : ActorMethod< + [GenerateTicketWithTxidArgs], + Result_1 + >, + 'get_deposit_address' : ActorMethod<[string, string], Result_2>, + 'get_fee_payment_address' : ActorMethod<[], Result_2>, + 'get_finalized_lock_ticket_txids' : ActorMethod<[], Array>, + 'get_finalized_unlock_ticket_results' : ActorMethod< + [], + Array + >, + 'get_platform_fee' : ActorMethod<[string], [[] | [bigint], [] | [string]]>, + 'get_token_list' : ActorMethod<[], Array>, + 'init_ecdsa_public_key' : ActorMethod<[], Result_1>, + 'pending_unlock_tickets' : ActorMethod<[bigint], string>, + 'query_finalized_lock_tickets' : ActorMethod< + [string], + [] | [LockTicketRequest] + >, + 'query_state' : ActorMethod<[], StateProfile>, + 'release_token_status' : ActorMethod<[string], ReleaseTokenStatus>, + 'resend_unlock_ticket' : ActorMethod<[bigint, [] | [bigint]], Result_3>, + 'save_utxo_for_payment_address' : ActorMethod<[string], Result_4>, + 'set_default_doge_rpc_config' : ActorMethod< + [string, [] | [string]], + undefined + >, + 'set_fee_collector' : ActorMethod<[string], undefined>, + 'set_min_deposit_amount' : ActorMethod<[bigint], undefined>, + 'set_multi_rpc_config' : ActorMethod<[MultiRpcConfig], undefined>, + 'set_tatum_api_config' : ActorMethod<[string, [] | [string]], undefined>, +} +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/assets/doge_customs/service.did.js b/assets/doge_customs/service.did.js new file mode 100644 index 00000000..e29a58ab --- /dev/null +++ b/assets/doge_customs/service.did.js @@ -0,0 +1,232 @@ +export const idlFactory = ({ IDL }) => { + const RpcConfig = IDL.Record({ + 'url' : IDL.Text, + 'api_key' : IDL.Opt(IDL.Text), + }); + const InitArgs = IDL.Record({ + 'fee_token' : IDL.Text, + 'hub_principal' : IDL.Principal, + 'chain_id' : IDL.Text, + 'default_doge_rpc_config' : RpcConfig, + 'admins' : IDL.Vec(IDL.Principal), + }); + const GenerateTicketArgs = IDL.Record({ + 'token_id' : IDL.Text, + 'target_chain_id' : IDL.Text, + 'receiver' : IDL.Text, + }); + const Destination = IDL.Record({ + 'token' : IDL.Opt(IDL.Text), + 'target_chain_id' : IDL.Text, + 'receiver' : IDL.Text, + }); + const CustomsError = IDL.Variant({ + 'SendTicketErr' : IDL.Text, + 'RpcError' : IDL.Text, + 'TemporarilyUnavailable' : IDL.Text, + 'HttpOutCallError' : IDL.Tuple(IDL.Text, IDL.Text, IDL.Text), + 'AlreadyProcessed' : IDL.Null, + 'HttpStatusError' : IDL.Tuple(IDL.Nat, IDL.Text, IDL.Text), + 'OrdTxError' : IDL.Text, + 'NotBridgeTx' : IDL.Null, + 'AmountIsZero' : IDL.Null, + 'InvalidRuneId' : IDL.Text, + 'InvalidArgs' : IDL.Text, + 'AlreadySubmitted' : IDL.Null, + 'InvalidTxId' : IDL.Null, + 'NotPayFees' : IDL.Null, + 'CallError' : IDL.Tuple(IDL.Principal, IDL.Text, IDL.Text), + 'TxNotFoundInMemPool' : IDL.Null, + 'Unknown' : IDL.Null, + 'InvalidTxReceiver' : IDL.Null, + 'UnsupportedChainId' : IDL.Text, + 'ECDSAPublicKeyNotFound' : IDL.Null, + 'HttpOutExceedRetryLimit' : IDL.Null, + 'DepositUtxoNotFound' : IDL.Tuple(IDL.Text, Destination), + 'UnsupportedToken' : IDL.Text, + 'CustomError' : IDL.Text, + }); + const Result = IDL.Variant({ + 'Ok' : IDL.Vec(IDL.Text), + 'Err' : CustomsError, + }); + const GenerateTicketWithTxidArgs = IDL.Record({ + 'token_id' : IDL.Text, + 'txid' : IDL.Text, + 'target_chain_id' : IDL.Text, + 'receiver' : IDL.Text, + }); + const Result_1 = IDL.Variant({ 'Ok' : IDL.Null, 'Err' : CustomsError }); + const Result_2 = IDL.Variant({ 'Ok' : IDL.Text, 'Err' : CustomsError }); + const SendTicketResult = IDL.Record({ + 'txid' : IDL.Vec(IDL.Nat8), + 'success' : IDL.Bool, + 'time_at' : IDL.Nat64, + }); + const TokenResp = IDL.Record({ + 'decimals' : IDL.Nat8, + 'token_id' : IDL.Text, + 'icon' : IDL.Opt(IDL.Text), + 'symbol' : IDL.Text, + }); + const LockTicketRequest = IDL.Record({ + 'received_at' : IDL.Nat64, + 'transaction_hex' : IDL.Text, + 'token_id' : IDL.Text, + 'txid' : IDL.Vec(IDL.Nat8), + 'target_chain_id' : IDL.Text, + 'amount' : IDL.Text, + 'receiver' : IDL.Text, + }); + const Utxo = IDL.Record({ + 'value' : IDL.Nat64, + 'txid' : IDL.Vec(IDL.Nat8), + 'vout' : IDL.Nat32, + }); + const EcdsaPublicKeyResponse = IDL.Record({ + 'public_key' : IDL.Vec(IDL.Nat8), + 'chain_code' : IDL.Vec(IDL.Nat8), + }); + const Token = IDL.Record({ + 'decimals' : IDL.Nat8, + 'token_id' : IDL.Text, + 'metadata' : IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + 'icon' : IDL.Opt(IDL.Text), + 'name' : IDL.Text, + 'symbol' : IDL.Text, + }); + const MultiRpcConfig = IDL.Record({ + 'rpc_list' : IDL.Vec(RpcConfig), + 'minimum_response_count' : IDL.Nat32, + }); + const ChainState = IDL.Variant({ + 'Active' : IDL.Null, + 'Deactive' : IDL.Null, + }); + const ChainType = IDL.Variant({ + 'SettlementChain' : IDL.Null, + 'ExecutionChain' : IDL.Null, + }); + const Chain = IDL.Record({ + 'fee_token' : IDL.Opt(IDL.Text), + 'canister_id' : IDL.Text, + 'chain_id' : IDL.Text, + 'counterparties' : IDL.Opt(IDL.Vec(IDL.Text)), + 'chain_state' : ChainState, + 'chain_type' : ChainType, + 'contract_address' : IDL.Opt(IDL.Text), + }); + const StateProfile = IDL.Record({ + 'next_consume_ticket_seq' : IDL.Nat64, + 'fee_token' : IDL.Text, + 'hub_principal' : IDL.Principal, + 'ecdsa_key_name' : IDL.Text, + 'doge_chain' : IDL.Nat8, + 'next_directive_seq' : IDL.Nat64, + 'doge_fee_rate' : IDL.Opt(IDL.Nat64), + 'deposited_utxo' : IDL.Vec(IDL.Tuple(Utxo, Destination)), + 'fee_collector' : IDL.Text, + 'ecdsa_public_key' : IDL.Opt(EcdsaPublicKeyResponse), + 'chain_id' : IDL.Text, + 'pending_lock_ticket_requests' : IDL.Vec( + IDL.Tuple(IDL.Text, LockTicketRequest) + ), + 'tokens' : IDL.Vec(IDL.Tuple(IDL.Text, Token)), + 'admins' : IDL.Vec(IDL.Principal), + 'target_chain_factor' : IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat)), + 'multi_rpc_config' : MultiRpcConfig, + 'counterparties' : IDL.Vec(IDL.Tuple(IDL.Text, Chain)), + 'min_deposit_amount' : IDL.Nat64, + 'next_ticket_seq' : IDL.Nat64, + 'chain_state' : ChainState, + 'min_confirmations' : IDL.Nat32, + 'tatum_rpc_config' : RpcConfig, + 'fee_payment_utxo' : IDL.Vec(Utxo), + 'flight_unlock_ticket_map' : IDL.Vec( + IDL.Tuple(IDL.Nat64, SendTicketResult) + ), + 'fee_token_factor' : IDL.Opt(IDL.Nat), + }); + const ReleaseTokenStatus = IDL.Variant({ + 'Signing' : IDL.Null, + 'Confirmed' : IDL.Text, + 'Sending' : IDL.Text, + 'Unknown' : IDL.Null, + 'Submitted' : IDL.Text, + 'Pending' : IDL.Null, + }); + const Result_3 = IDL.Variant({ 'Ok' : IDL.Text, 'Err' : IDL.Text }); + const Result_4 = IDL.Variant({ 'Ok' : IDL.Nat64, 'Err' : CustomsError }); + return IDL.Service({ + 'generate_ticket' : IDL.Func([GenerateTicketArgs], [Result], []), + 'generate_ticket_by_txid' : IDL.Func( + [GenerateTicketWithTxidArgs], + [Result_1], + [], + ), + 'get_deposit_address' : IDL.Func( + [IDL.Text, IDL.Text], + [Result_2], + ['query'], + ), + 'get_fee_payment_address' : IDL.Func([], [Result_2], ['query']), + 'get_finalized_lock_ticket_txids' : IDL.Func( + [], + [IDL.Vec(IDL.Text)], + ['query'], + ), + 'get_finalized_unlock_ticket_results' : IDL.Func( + [], + [IDL.Vec(SendTicketResult)], + ['query'], + ), + 'get_platform_fee' : IDL.Func( + [IDL.Text], + [IDL.Opt(IDL.Nat), IDL.Opt(IDL.Text)], + ['query'], + ), + 'get_token_list' : IDL.Func([], [IDL.Vec(TokenResp)], ['query']), + 'init_ecdsa_public_key' : IDL.Func([], [Result_1], []), + 'pending_unlock_tickets' : IDL.Func([IDL.Nat64], [IDL.Text], ['query']), + 'query_finalized_lock_tickets' : IDL.Func( + [IDL.Text], + [IDL.Opt(LockTicketRequest)], + ['query'], + ), + 'query_state' : IDL.Func([], [StateProfile], ['query']), + 'release_token_status' : IDL.Func( + [IDL.Text], + [ReleaseTokenStatus], + ['query'], + ), + 'resend_unlock_ticket' : IDL.Func( + [IDL.Nat64, IDL.Opt(IDL.Nat64)], + [Result_3], + [], + ), + 'save_utxo_for_payment_address' : IDL.Func([IDL.Text], [Result_4], []), + 'set_default_doge_rpc_config' : IDL.Func( + [IDL.Text, IDL.Opt(IDL.Text)], + [], + [], + ), + 'set_fee_collector' : IDL.Func([IDL.Text], [], []), + 'set_min_deposit_amount' : IDL.Func([IDL.Nat64], [], []), + 'set_multi_rpc_config' : IDL.Func([MultiRpcConfig], [], []), + 'set_tatum_api_config' : IDL.Func([IDL.Text, IDL.Opt(IDL.Text)], [], []), + }); +}; +export const init = ({ IDL }) => { + const RpcConfig = IDL.Record({ + 'url' : IDL.Text, + 'api_key' : IDL.Opt(IDL.Text), + }); + const InitArgs = IDL.Record({ + 'fee_token' : IDL.Text, + 'hub_principal' : IDL.Principal, + 'chain_id' : IDL.Text, + 'default_doge_rpc_config' : RpcConfig, + 'admins' : IDL.Vec(IDL.Principal), + }); + return [InitArgs]; +}; diff --git a/canister_ids.json b/canister_ids.json index 07dc7b7a..3b67b399 100644 --- a/canister_ids.json +++ b/canister_ids.json @@ -17,6 +17,9 @@ "cosmwasm_proxy": { "ic": "ncfbq-kyaaa-aaaar-qah3a-cai" }, + "doge_customs": { + "ic": "nnqqa-aaaaa-aaaar-qamla-cai" + }, "generic_proxy": { "ic": "c2bzs-5iaaa-aaaar-qakta-cai" }, diff --git a/customs/doge/Cargo.lock b/customs/doge/Cargo.lock new file mode 100644 index 00000000..8ed5c4b1 --- /dev/null +++ b/customs/doge/Cargo.lock @@ -0,0 +1,3145 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "lazy_static", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bitcoin" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" +dependencies = [ + "base58ck", + "bech32 0.11.0", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-unit" +version = "4.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" +dependencies = [ + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "candid" +version = "0.10.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04aa85a9ba2542bded33d1eff0ffb17cb98b1be8117e0a25e1ad8c62bedc881" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive", + "hex", + "ic_principal", + "leb128", + "num-bigint", + "num-traits", + "paste", + "pretty", + "serde", + "serde_bytes", + "stacker", + "thiserror", +] + +[[package]] +name = "candid_derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "cc" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half 2.4.1", +] + +[[package]] +name = "comparable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8606f9aa5b5a2df738584b139c79413d0c1545ed0ffd16e76e0944d1de7388c0" +dependencies = [ + "comparable_derive", + "comparable_helper", + "pretty_assertions", + "serde", +] + +[[package]] +name = "comparable_derive" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f36ea7383b9a2a9ae0a4e225d8a9c1c3aeadde78c59cdc35bad5c02b4dad01" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "comparable_helper" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c9b60259084f32c14d32476f3a299b4997e3c186e1473bd972ff8a8c83d1b4" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cvt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.93", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core 0.20.10", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.8-alpha.0" +source = "git+https://github.com/dfinity-lab/derive_more?rev=9f1b894e6fde640da4e9ea71a8fc0e4dd98d01da#9f1b894e6fde640da4e9ea71a8fc0e4dd98d01da" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "doge_customs" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bech32 0.9.1", + "bitcoin", + "bs58", + "candid", + "ciborium", + "getrandom", + "hex-conservative", + "ic-base-types", + "ic-canister-log", + "ic-canisters-http-types", + "ic-cdk", + "ic-cdk-macros", + "ic-cdk-timers", + "ic-crypto-extended-bip32", + "ic-crypto-getrandom-for-wasm", + "ic-crypto-sha2", + "ic-ic00-types", + "ic-icrc1", + "ic-metrics-encoder", + "ic-stable-structures 0.6.7", + "ic-utils-ensure", + "ic0 0.18.11", + "lazy_static", + "log", + "minicbor", + "minicbor-derive", + "num-traits", + "omnity_types", + "rand", + "ripemd", + "rust_decimal", + "rust_decimal_macros", + "scopeguard", + "serde", + "serde_bytes", + "serde_json", + "serde_with 3.12.0", + "thiserror", +] + +[[package]] +name = "ecdsa" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.9", + "digest", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature", + "spki 0.7.3", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "pem-rfc7468", + "pkcs8 0.10.2", + "rand_core", + "sec1 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "env_filter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6dc8c8ff84895b051f07a0e65f975cf225131742531338752abfb324e4449ff" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fe-derive" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "hex", + "num-bigint-dig", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.6", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ic-base-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "base32", + "byte-unit", + "bytes", + "candid", + "comparable", + "crc32fast", + "ic-crypto-sha2", + "ic-protobuf", + "ic-stable-structures 0.5.6", + "phantom_newtype", + "prost", + "serde", + "strum", + "strum_macros", +] + +[[package]] +name = "ic-btc-interface" +version = "0.1.0" +source = "git+https://github.com/dfinity/bitcoin-canister?rev=9b239d1d67253eb14a35be6061e3967d5ec9db9d#9b239d1d67253eb14a35be6061e3967d5ec9db9d" +dependencies = [ + "candid", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-btc-types-internal" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "ic-btc-interface", + "ic-error-types", + "ic-protobuf", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-canister-log" +version = "0.2.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "serde", +] + +[[package]] +name = "ic-canisters-http-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-03-06_23-01%2Bp2p#fff20526e154f8b8d24373efd9b50f588d147e91" +dependencies = [ + "candid", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e908da565d9e304e83732500069ebb959e3d2cad80f894889ea37207112c7a0" +dependencies = [ + "candid", + "ic-cdk-macros", + "ic0 0.21.1", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a618e4020cea88e933d8d2f8c7f86d570ec06213506a80d4f2c520a9bba512" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream", + "syn 1.0.109", +] + +[[package]] +name = "ic-cdk-timers" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c43b9706fef3ad10c4192a14801d16bd9539068239f0f06f257857441364329" +dependencies = [ + "futures", + "ic-cdk", + "ic0 0.21.1", + "serde", + "serde_bytes", + "slotmap", +] + +[[package]] +name = "ic-constants" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" + +[[package]] +name = "ic-crypto-extended-bip32" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "ic-crypto-internal-threshold-sig-ecdsa", +] + +[[package]] +name = "ic-crypto-getrandom-for-wasm" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ic-crypto-internal-hmac" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "ic-crypto-internal-sha2", +] + +[[package]] +name = "ic-crypto-internal-seed" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "hex", + "ic-crypto-sha2", + "ic-types", + "rand", + "rand_chacha", + "serde", + "zeroize", +] + +[[package]] +name = "ic-crypto-internal-sha2" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "sha2", +] + +[[package]] +name = "ic-crypto-internal-threshold-sig-ecdsa" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "fe-derive", + "hex", + "hex-literal", + "ic-crypto-internal-hmac", + "ic-crypto-internal-seed", + "ic-crypto-internal-types", + "ic-crypto-secrets-containers", + "ic-crypto-sha2", + "ic-types", + "k256 0.13.4", + "lazy_static", + "p256", + "paste", + "rand", + "rand_chacha", + "serde", + "serde_bytes", + "serde_cbor", + "strum", + "strum_macros", + "subtle", + "zeroize", +] + +[[package]] +name = "ic-crypto-internal-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "arrayvec 0.7.6", + "hex", + "ic-protobuf", + "phantom_newtype", + "serde", + "serde_cbor", + "strum", + "strum_macros", + "thiserror", + "zeroize", +] + +[[package]] +name = "ic-crypto-secrets-containers" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "ic-crypto-sha2" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "ic-crypto-internal-sha2", +] + +[[package]] +name = "ic-crypto-tree-hash" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "assert_matches", + "ic-crypto-internal-types", + "ic-crypto-sha2", + "ic-protobuf", + "serde", + "serde_bytes", + "thiserror", +] + +[[package]] +name = "ic-error-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "ic-utils", + "serde", + "strum", + "strum_macros", +] + +[[package]] +name = "ic-ic00-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "ic-base-types", + "ic-btc-interface", + "ic-btc-types-internal", + "ic-error-types", + "ic-protobuf", + "num-traits", + "serde", + "serde_bytes", + "serde_cbor", + "strum", + "strum_macros", +] + +[[package]] +name = "ic-icrc1" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "ciborium", + "hex", + "ic-base-types", + "ic-crypto-sha2", + "ic-ledger-canister-core", + "ic-ledger-core", + "ic-ledger-hash-of", + "icrc-ledger-types", + "num-bigint", + "num-traits", + "serde", + "serde_bytes", + "tempfile", + "thiserror", +] + +[[package]] +name = "ic-ledger-canister-core" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "async-trait", + "candid", + "ic-base-types", + "ic-canister-log", + "ic-constants", + "ic-ic00-types", + "ic-ledger-core", + "ic-ledger-hash-of", + "ic-utils", + "num-traits", + "serde", +] + +[[package]] +name = "ic-ledger-core" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "ic-ledger-hash-of", + "num-traits", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-ledger-hash-of" +version = "0.1.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "hex", + "serde", +] + +[[package]] +name = "ic-metrics-encoder" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5c7628eac357aecda461130f8074468be5aa4d258a002032d82d817f79f1f8" + +[[package]] +name = "ic-protobuf" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "bincode", + "candid", + "erased-serde", + "maplit", + "prost", + "serde", + "serde_json", + "slog", +] + +[[package]] +name = "ic-stable-structures" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95dce29e3ceb0e6da3e78b305d95365530f2efd2146ca18590c0ef3aa6038568" + +[[package]] +name = "ic-stable-structures" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b492c5a16455ae78623eaa12ead96dda6c69a83c535b1b00789f19b381c8a24c" +dependencies = [ + "ic_principal", +] + +[[package]] +name = "ic-sys" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "hex", + "ic-crypto-sha2", + "lazy_static", + "libc", + "nix", + "phantom_newtype", + "tokio", + "wsl", +] + +[[package]] +name = "ic-types" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "base64 0.13.1", + "bincode", + "candid", + "chrono", + "derive_more 0.99.8-alpha.0", + "hex", + "ic-base-types", + "ic-btc-types-internal", + "ic-constants", + "ic-crypto-internal-types", + "ic-crypto-sha2", + "ic-crypto-tree-hash", + "ic-error-types", + "ic-ic00-types", + "ic-protobuf", + "ic-utils", + "maplit", + "once_cell", + "phantom_newtype", + "prost", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "serde_with 1.14.0", + "strum", + "strum_macros", + "thiserror", + "thousands", +] + +[[package]] +name = "ic-utils" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "cvt", + "hex", + "ic-sys", + "libc", + "nix", + "prost", + "rand", + "scoped_threadpool", + "serde", + "thiserror", +] + +[[package]] +name = "ic-utils-ensure" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" + +[[package]] +name = "ic0" +version = "0.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576c539151d4769fb4d1a0c25c4108dd18facd04c5695b02cf2d226ab4e43aa5" + +[[package]] +name = "ic0" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54b5297861c651551676e8c43df805dad175cc33bc97dbd992edbbb85dcbcdf" + +[[package]] +name = "ic_principal" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +dependencies = [ + "crc32fast", + "data-encoding", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "icrc-ledger-types" +version = "0.1.4" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "base32", + "candid", + "crc32fast", + "hex", + "num-bigint", + "num-traits", + "serde", + "serde_bytes", + "sha2", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397" +dependencies = [ + "cfg-if", + "ecdsa 0.15.1", + "elliptic-curve 0.12.3", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "once_cell", + "sha2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minicbor" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "omnity_types" +version = "0.1.0" +dependencies = [ + "candid", + "ciborium", + "derive_more 0.99.18", + "env_filter", + "getrandom", + "hex", + "humantime", + "ic-canister-log", + "ic-canisters-http-types", + "ic-cdk", + "ic-stable-structures 0.6.7", + "k256 0.12.0", + "lazy_static", + "log", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_derive", + "serde_json", + "serde_with 3.12.0", + "sha2", + "thiserror", + "time", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "phantom_newtype" +version = "0.9.0" +source = "git+https://github.com/dfinity/ic?tag=release-2024-01-18_23-01#a7862784e8da4a97a1d608fd5b3db365de41a2d7" +dependencies = [ + "candid", + "serde", + "slog", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +dependencies = [ + "arrayvec 0.5.2", + "typed-arena", + "unicode-width", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "psm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec 0.7.6", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da991f231869f34268415a49724c6578e740ad697ba0999199d6f22b3949332c" +dependencies = [ + "quote", + "rust_decimal", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_tokenstream" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9" +dependencies = [ + "proc-macro2", + "serde", + "syn 1.0.109", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros 1.5.2", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros 3.12.0", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" +dependencies = [ + "erased-serde", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", +] + +[[package]] +name = "stacker" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.93", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.7.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.93", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wsl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dab7ac864710bdea6594becbea5b5050333cf34fefb0dc319567eb347950d4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] diff --git a/customs/doge/Cargo.toml b/customs/doge/Cargo.toml new file mode 100644 index 00000000..5993fb7c --- /dev/null +++ b/customs/doge/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "doge_customs" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# async-trait = "0.1.53" +bech32 = "0.9.0" +bs58 = "0.4.0" +bitcoin = { version = "=0.32.2", default-features = false, features = [ + "std", + "secp-lowmemory", +] } +# bitcoin = { version = "=0.32.2", features = ["rand", "serde", "std", "secp-lowmemory"] } +bitcoin-io = "=0.1.2" +# candid = { workspace = true } +candid = { version = "0.10" } +# ciborium = { workspace = true } +ciborium = "0.2.1" +hex = { package = "hex-conservative", version = "0.2", default-features = false, features = [ + "alloc", +] } +ic0 = "0.18.9" +base64 = "0.22.1" +ic-base-types = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +# ic-btc-interface = { workspace = true } +ic-canisters-http-types = { git = "https://github.com/dfinity/ic", tag = "release-2024-03-06_23-01+p2p" } +ic-canister-log = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +# ic-cdk = { workspace = true } +# ic-cdk-macros = { workspace = true } +ic-cdk = "0.12.2" +ic-cdk-macros = "0.8.3" +ic-cdk-timers = "0.6" +ic-crypto-extended-bip32 = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +ic-crypto-getrandom-for-wasm = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +ic-crypto-sha2 = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +ic-ic00-types = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +ic-icrc1 = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +ic-metrics-encoder = "1" +# ic-stable-structures = { workspace = true } +ic-stable-structures = "0.6.5" +ic-utils-ensure = { git = "https://github.com/dfinity/ic", tag = "release-2024-01-18_23-01" } +lazy_static = "1.4.0" +# minicbor = { workspace = true } +# minicbor-derive = { workspace = true } +minicbor = { version = "0.19.1", features = ["alloc", "derive"] } +minicbor-derive = "0.13.0" +num-traits = "0.2.14" +ripemd = "0.1.1" +scopeguard = "1.1.0" +# serde = { workspace = true } +# serde_bytes = { workspace = true } +# serde_json = { workspace = true } +serde = { version = "1", features = ["derive"] } +serde_bytes = "0.11" +serde_json = { version = "1", features = ["std"] } +# omnity_types = { path = "../../types" } +omnity_types = { git = "https://github.com/octopus-network/omnity-interoperability.git", rev = "9e851c7a1a37a997fb9ee09865fb2e54fd4e79ed"} +thiserror = "1.0.63" +rand = "0.8.5" +serde_with = { version = "3", default-features = false, features = ["macros"] } +anyhow = "1" +getrandom = "0.2.15" +k256 = { git = "https://github.com/altkdf/elliptic-curves", branch = "schnorr_canister", features = ["schnorr"] } +# rust_decimal = {workspace = true } +# rust_decimal_macros = {workspace = true} +rust_decimal = "1.36" +rust_decimal_macros = "1.36" +log = "0.4.22" +bincode = "1.3.3" +futures = "0.3.30" \ No newline at end of file diff --git a/customs/doge/doge_customs.did b/customs/doge/doge_customs.did new file mode 100644 index 00000000..e4a0474b --- /dev/null +++ b/customs/doge/doge_customs.did @@ -0,0 +1,153 @@ +type Chain = record { + fee_token : opt text; + canister_id : text; + chain_id : text; + counterparties : opt vec text; + chain_state : ChainState; + chain_type : ChainType; + contract_address : opt text; +}; +type ChainState = variant { Active; Deactive }; +type ChainType = variant { SettlementChain; ExecutionChain }; +type CustomsError = variant { + SendTicketErr : text; + RpcError : text; + TemporarilyUnavailable : text; + HttpOutCallError : record { text; text; text }; + AlreadyProcessed; + HttpStatusError : record { nat; text; text }; + OrdTxError : text; + NotBridgeTx; + AmountIsZero; + InvalidRuneId : text; + InvalidArgs : text; + AlreadySubmitted; + InvalidTxId; + NotPayFees; + CallError : record { principal; text; text }; + TxNotFoundInMemPool; + Unknown; + InvalidTxReceiver; + UnsupportedChainId : text; + ECDSAPublicKeyNotFound; + HttpOutExceedRetryLimit; + DepositUtxoNotFound : record { text; Destination }; + UnsupportedToken : text; + CustomError : text; +}; +type Destination = record { + token : opt text; + target_chain_id : text; + receiver : text; +}; +type EcdsaPublicKeyResponse = record { public_key : blob; chain_code : blob }; +type GenerateTicketArgs = record { + token_id : text; + target_chain_id : text; + receiver : text; +}; +type GenerateTicketWithTxidArgs = record { + token_id : text; + txid : text; + target_chain_id : text; + receiver : text; +}; +type InitArgs = record { + fee_token : text; + hub_principal : principal; + chain_id : text; + default_doge_rpc_config : RpcConfig; + admins : vec principal; +}; +type LockTicketRequest = record { + received_at : nat64; + transaction_hex : text; + token_id : text; + txid : blob; + target_chain_id : text; + amount : text; + receiver : text; +}; +type MultiRpcConfig = record { + rpc_list : vec RpcConfig; + minimum_response_count : nat32; +}; +type ReleaseTokenStatus = variant { + Signing; + Confirmed : text; + Sending : text; + Unknown; + Submitted : text; + Pending; +}; +type Result = variant { Ok : vec text; Err : CustomsError }; +type Result_1 = variant { Ok; Err : CustomsError }; +type Result_2 = variant { Ok : text; Err : CustomsError }; +type Result_3 = variant { Ok : text; Err : text }; +type Result_4 = variant { Ok : nat64; Err : CustomsError }; +type RpcConfig = record { url : text; api_key : opt text }; +type SendTicketResult = record { txid : blob; success : bool; time_at : nat64 }; +type StateProfile = record { + next_consume_ticket_seq : nat64; + fee_token : text; + hub_principal : principal; + ecdsa_key_name : text; + doge_chain : nat8; + next_directive_seq : nat64; + doge_fee_rate : opt nat64; + deposited_utxo : vec record { Utxo; Destination }; + fee_collector : text; + ecdsa_public_key : opt EcdsaPublicKeyResponse; + chain_id : text; + pending_lock_ticket_requests : vec record { text; LockTicketRequest }; + tokens : vec record { text; Token }; + admins : vec principal; + target_chain_factor : vec record { text; nat }; + multi_rpc_config : MultiRpcConfig; + counterparties : vec record { text; Chain }; + min_deposit_amount : nat64; + next_ticket_seq : nat64; + chain_state : ChainState; + min_confirmations : nat32; + tatum_rpc_config : RpcConfig; + fee_payment_utxo : vec Utxo; + flight_unlock_ticket_map : vec record { nat64; SendTicketResult }; + fee_token_factor : opt nat; +}; +type Token = record { + decimals : nat8; + token_id : text; + metadata : vec record { text; text }; + icon : opt text; + name : text; + symbol : text; +}; +type TokenResp = record { + decimals : nat8; + token_id : text; + icon : opt text; + symbol : text; +}; +type Utxo = record { value : nat64; txid : blob; vout : nat32 }; +service : (InitArgs) -> { + generate_ticket : (GenerateTicketArgs) -> (Result); + generate_ticket_by_txid : (GenerateTicketWithTxidArgs) -> (Result_1); + get_deposit_address : (text, text) -> (Result_2) query; + get_fee_payment_address : () -> (Result_2) query; + get_finalized_lock_ticket_txids : () -> (vec text) query; + get_finalized_unlock_ticket_results : () -> (vec SendTicketResult) query; + get_platform_fee : (text) -> (opt nat, opt text) query; + get_token_list : () -> (vec TokenResp) query; + init_ecdsa_public_key : () -> (Result_1); + pending_unlock_tickets : (nat64) -> (text) query; + query_finalized_lock_tickets : (text) -> (opt LockTicketRequest) query; + query_state : () -> (StateProfile) query; + release_token_status : (text) -> (ReleaseTokenStatus) query; + resend_unlock_ticket : (nat64, opt nat64) -> (Result_3); + save_utxo_for_payment_address : (text) -> (Result_4); + set_default_doge_rpc_config : (text, opt text) -> (); + set_fee_collector : (text) -> (); + set_min_deposit_amount : (nat64) -> (); + set_multi_rpc_config : (MultiRpcConfig) -> (); + set_tatum_api_config : (text, opt text) -> (); +} diff --git a/customs/doge/src/audit/mod.rs b/customs/doge/src/audit/mod.rs new file mode 100644 index 00000000..e12c3bd7 --- /dev/null +++ b/customs/doge/src/audit/mod.rs @@ -0,0 +1,10 @@ +use crate::state::DogeState; +use omnity_types::{Chain, Token}; + +pub fn add_chain(state: &mut DogeState, chain: Chain) { + state.counterparties.insert(chain.chain_id.clone(), chain); +} + +pub fn add_token(state: &mut DogeState, token: Token) { + state.tokens.insert(token.token_id.clone(), token); +} diff --git a/customs/doge/src/call_error.rs b/customs/doge/src/call_error.rs new file mode 100644 index 00000000..e676336e --- /dev/null +++ b/customs/doge/src/call_error.rs @@ -0,0 +1,58 @@ +use ic_cdk::api::call::RejectionCode; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Represents an error from a management canister call, such as +/// `sign_with_ecdsa` or `bitcoin_send_transaction`. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct CallError { + pub method: String, + pub reason: Reason, +} + +impl fmt::Display for CallError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + fmt, + "management call '{}' failed: {}", + self.method, self.reason + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// The reason for the management call failure. +pub enum Reason { + /// Failed to send a signature request because the local output queue is + /// full. + QueueIsFull, + /// The canister does not have enough cycles to submit the request. + OutOfCycles, + /// The call failed with an error. + CanisterError(String), + /// The management canister rejected the signature request (not enough + /// cycles, the ECDSA subnet is overloaded, etc.). + Rejected(String), +} + +impl fmt::Display for Reason { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::QueueIsFull => write!(fmt, "the canister queue is full"), + Self::OutOfCycles => write!(fmt, "the canister is out of cycles"), + Self::CanisterError(msg) => write!(fmt, "canister error: {}", msg), + Self::Rejected(msg) => { + write!(fmt, "the management canister rejected the call: {}", msg) + } + } + } +} + +impl Reason { + pub fn from_reject(reject_code: RejectionCode, reject_message: String) -> Self { + match reject_code { + RejectionCode::CanisterReject => Self::Rejected(reject_message), + _ => Self::CanisterError(reject_message), + } + } +} diff --git a/customs/doge/src/custom_to_dogecoin.rs b/customs/doge/src/custom_to_dogecoin.rs new file mode 100644 index 00000000..a1860031 --- /dev/null +++ b/customs/doge/src/custom_to_dogecoin.rs @@ -0,0 +1,431 @@ +use std::str::FromStr; + +use bitcoin::ecdsa::Signature as SighashSignature; +use bitcoin::secp256k1::{ecdsa::Signature, PublicKey}; +use bitcoin::EcdsaSighashType; +use candid::CandidType; + +use ic_canister_log::log; + +use ic_stable_structures::storable::Bound; +use ic_stable_structures::Storable; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::constants::{FINALIZE_UNLOCK_TICKET_NAME, SUBMIT_UNLOCK_TICKETS_NAME}; +use crate::doge::ecdsa::sign_with; +use crate::doge::fee::{fee_by_size, DOGE_AMOUNT, DUST_LIMIT}; +use crate::doge::rpc::DogeRpc; +use crate::doge::script; +use crate::doge::sighash::SighashCache; +use crate::doge::transaction::{OutPoint, Transaction, TxIn, TxOut}; +use crate::types::{Destination, Txid, Utxo}; +use omnity_types::ic_log::{CRITICAL, ERROR, INFO}; +use omnity_types::Seq; + +use crate::custom_to_dogecoin::CustomToBitcoinError::{ + ArgumentError, SendTransactionFailed, SignFailed, +}; + +use crate::hub::update_tx_hash; + +use crate::state::{finalization_time_estimate, mutate_state, read_state}; + +#[derive(Error, Debug, CandidType)] +pub enum CustomToBitcoinError { + #[error("bitcoin sign error: {0}")] + SignFailed(String), + #[error("ArgumentError: {0}")] + ArgumentError(String), + #[error("InsufficientFunds")] + InsufficientFunds, + #[error("InsufficientFee")] + InsufficientFee, + #[error("AmountTooSmall")] + AmountTooSmall, + #[error("SendTransactionFailed: {0}")] + SendTransactionFailed(String), +} +pub type CustomToBitcoinResult = Result; + +#[derive(Serialize, Deserialize, Clone, CandidType)] +pub struct SendTicketResult { + pub txid: Txid, + pub success: bool, + pub time_at: u64, +} + +impl Storable for SendTicketResult { + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + bincode::serialize(self).unwrap().into() + } + + fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { + bincode::deserialize(bytes.as_ref()).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +pub async fn send_tickets_to_bitcoin() { + let (from, to, fee_rate) = read_state(|s| { + ( + s.next_consume_ticket_seq, + s.next_ticket_seq, + s.doge_fee_rate, + ) + }); + if from < to { + log!(INFO, "submit unlock tx: from {} to {}", from, to); + for seq in from..to { + let r = process_unlock_ticket(seq, fee_rate).await; + match r { + Ok(_) => { + mutate_state(|s| s.next_consume_ticket_seq = seq + 1); + } + Err(e) => { + log!(ERROR, "send unlock error: ticket seq: {}, error{}", seq, e); + break; + } + } + } + } +} + +pub async fn process_unlock_ticket( + seq: Seq, + fee_rate: Option, +) -> Result<(), CustomToBitcoinError> { + let res = submit_unlock_ticket(seq, fee_rate).await; + if res.is_err() { + let err = res.err().unwrap(); + log!( + CRITICAL, + "send ticket to bitcoin failed, ticket seq: {}, {}", + seq, + &err + ); + return Err(err); + } else { + let r = res.ok().unwrap(); + log!( + INFO, + "process ticket to bitcoin success, ticket seq: {}, txid: {}", + seq, + r.txid + ); + mutate_state(|s| { + s.flight_unlock_ticket_map.insert(seq,r); + }); + } + Ok(()) +} + +pub async fn finalize_flight_unlock_tickets() { + let now = ic_cdk::api::time(); + let can_check_finalizations = read_state(|s| { + let wait_time = finalization_time_estimate(s.min_confirmations); + s.flight_unlock_ticket_map + .iter() + .filter(|&req| (req.1.time_at + (wait_time.as_nanos() as u64) < now)) + .map(|req| (*req.0, req.1.clone())) + .collect::>() + }); + let min_confirmations = read_state(|s| s.min_confirmations); + + let doge_rpc: DogeRpc = read_state(|s| s.default_doge_rpc_config.clone()).into(); + for (seq, send_result) in can_check_finalizations.clone() { + let need_check_txid = send_result.txid.clone(); + let transfer_txid = need_check_txid.to_string(); + let tx = doge_rpc.get_tx_out(&transfer_txid).await; + match tx { + Ok(t) => { + if t.confirmations >= min_confirmations { + mutate_state(|s| { + let r = s.flight_unlock_ticket_map.remove(&seq).unwrap(); + s.finalized_unlock_ticket_results_map.insert(seq, r); + }); + let (hub_principal, ticket) = + read_state(|s| (s.hub_principal, s.tickets_queue.get(&seq).unwrap())); + if let Err(err) = + update_tx_hash(hub_principal, ticket.ticket_id, transfer_txid).await + { + log!( + CRITICAL, + "[rewrite tx_hash] failed to write brc20 release tx hash, reason: {}", + err + ); + } else { + log!(INFO, "unlock ticket finalize success! ticket seq: {}", seq); + } + } + } + Err(e) => { + log!(ERROR, "confirm flight ticket error: {:?}", e); + } + } + } +} + +pub async fn build_and_send_transaction( + fee_utxo: Vec, + utxos: Vec<(Utxo, Destination)>, + amount: u64, + total: u64, + fee_rate: Option, + receiver: script::Address, +) -> Result<(Transaction, crate::doge::transaction::Txid), CustomToBitcoinError> { + // build transaction + let all_utxos: Vec<(Utxo, Destination)> = utxos + .clone() + .into_iter() + .chain( + fee_utxo + .iter() + .map(|e| (e.clone(), Destination::fee_payment_address())), + ) + .collect(); + let chain_params = read_state(|s| s.chain_params()); + let total_fee_utxo_amount: u64 = fee_utxo.iter().map(|u| u.value).sum(); + let (fee_payment_address, _) = + read_state(|s| s.get_address(Destination::fee_payment_address())) + .map_err(|e| ArgumentError(e.to_string()))?; + let ( + key_name, + change_address_ret, + ) = read_state(|s| { + ( + s.ecdsa_key_name.clone(), + s.get_address(Destination::change_address()), + ) + }); + let (change_address, _) = change_address_ret.map_err(|e| ArgumentError(e.to_string()))?; + let mut send_tx = Transaction { + version: Transaction::CURRENT_VERSION, + lock_time: 0, + input: utxos + .iter() + .map(|(utxo, _)| { + TxIn::with_outpoint(OutPoint { + txid: utxo.txid.clone().into(), + vout: utxo.vout, + }) + }) + .chain(fee_utxo.iter().map(|utxo| { + TxIn::with_outpoint(OutPoint { + txid: utxo.txid.clone().into(), + vout: utxo.vout, + }) + })) + .collect(), + output: vec![ + TxOut { + value: amount, + script_pubkey: receiver.to_script(chain_params), + }, + TxOut { + value: total.saturating_sub(amount), + script_pubkey: change_address.to_script(chain_params), + }, + TxOut { + value: total_fee_utxo_amount, + script_pubkey: fee_payment_address.to_script(chain_params), + }, + ], + }; + let fee = fee_by_size(send_tx.estimate_size() as u64, fee_rate); + log!( + INFO, + "send ticket fee: {}, total_fee_utxo_amount: {}", + fee, + total_fee_utxo_amount + ); + if fee > total_fee_utxo_amount { + return Err(CustomToBitcoinError::InsufficientFee); + } + send_tx.output[2].value = total_fee_utxo_amount.saturating_sub(fee); + if send_tx.output[2].value <= DUST_LIMIT { + send_tx.output.pop(); + } + + // sign transaction + + let mut sighasher = SighashCache::new(&mut send_tx); + for (i, (_utxo, destination)) in all_utxos.iter().enumerate() { + let (address, pk) = read_state(|s| s.get_address(destination.clone())) + .map_err(|e| ArgumentError(e.to_string()))?; + let hash = sighasher + .signature_hash(i, &address.to_script(chain_params), EcdsaSighashType::All) + .map_err(|e| SignFailed(e.to_string()))?; + let sig = sign_with(&key_name, destination.derivation_path(), *hash) + .await + .map_err(|e| SignFailed(e.to_string()))?; + let signature = Signature::from_compact(&sig).map_err(|e| SignFailed(e.to_string()))?; + sighasher + .set_input_script( + i, + &SighashSignature { + signature, + sighash_type: EcdsaSighashType::All, + }, + &PublicKey::from_slice(&pk).map_err(|e| SignFailed(e.to_string()))?, + ) + .map_err(|e| SignFailed(e.to_string()))?; + } + + // send transaction + let doge_rpc: DogeRpc = read_state(|s| s.default_doge_rpc_config.clone()).into(); + let txid = doge_rpc + .send_transaction(&send_tx) + .await + .map_err(|e| SendTransactionFailed(e.to_string()))?; + + Ok((send_tx, txid)) +} + +pub async fn submit_unlock_ticket( + seq: Seq, + fee_rate: Option, +) -> Result { + match read_state(|s| s.tickets_queue.get(&seq)) { + None => Err(CustomToBitcoinError::ArgumentError("ticket not found".to_string())), + Some(ticket) => { + // check ticket + if read_state(|s| s.finalized_unlock_ticket_results_map.contains_key(&seq)) { + return Err(CustomToBitcoinError::ArgumentError("ticket already finalized".to_string())); + } + if read_state(|s| s.flight_unlock_ticket_map.contains_key(&seq)) { + return Err(CustomToBitcoinError::ArgumentError("ticket already in flight".to_string())); + } + + let amount = ticket + .amount + .parse::() + .map_err(|e| ArgumentError(e.to_string()))?; + if amount < DUST_LIMIT * 10 { + return Err(CustomToBitcoinError::AmountTooSmall); + } + let chain_params = read_state(|s| s.chain_params()); + let receiver = script::Address::from_str(&ticket.receiver) + .map_err(|e| ArgumentError(e.to_string()))?; + if !receiver.is_p2pkh(chain_params) { + return Err(CustomToBitcoinError::ArgumentError( + "receiver address is not p2pkh".to_string(), + )); + } + + // select utxos + let (utxos, total) = select_utxos(amount)?; + let fee_utxo = mutate_state(|s| { + let fee_utxo = s.fee_payment_utxo.clone(); + s.fee_payment_utxo.clear(); + fee_utxo + }); + + match build_and_send_transaction(fee_utxo.clone(), utxos.clone(), amount, total, fee_rate, receiver).await { + Ok((send_tx, txid)) => { + mutate_state(|s| { + if send_tx.output.len() >= 2 { + // save change address utxo + s.deposited_utxo.push(( + Utxo { + txid: crate::types::Txid::from(txid).into(), + vout: 1, + value: send_tx.output[1].value, + }, + Destination::change_address(), + )); + } + + if send_tx.output.len() >= 3 { + // save fee payment utxo + s.fee_payment_utxo.push(Utxo { + txid: crate::types::Txid::from(txid).into(), + vout: 2, + value: send_tx.output[2].value, + }); + if s.fee_payment_utxo.iter().map(|u| u.value).sum::() < 5 * DOGE_AMOUNT { + log!(ERROR, "Doge Customs fee_payment_utxo will not enough soon!"); + } + } + }); + Ok(SendTicketResult { + txid: send_tx.compute_txid().into(), + success: true, + time_at: ic_cdk::api::time(), + }) + }, + Err(e) => { + mutate_state( + |s| { + s.fee_payment_utxo.append(&mut fee_utxo.clone()); + s.deposited_utxo.append(&mut utxos.clone()); + }, + ); + + return Err(e) + }, + } + } + } +} + +pub fn select_utxos(amount: u64) -> CustomToBitcoinResult<(Vec<(Utxo, Destination)>, u64)> { + let mut selected_utxos: Vec<(Utxo, Destination)> = vec![]; + let mut total = 0u64; + mutate_state(|s| { + while total < amount && s.deposited_utxo.len() > 0 { + let (utxo, d) = s + .deposited_utxo + .pop() + .ok_or(CustomToBitcoinError::InsufficientFunds)?; + total += utxo.value; + selected_utxos.push((utxo, d)); + } + Ok(()) + })?; + if total < amount { + return Err(CustomToBitcoinError::InsufficientFunds); + } + + Ok((selected_utxos, total)) +} + +pub fn finalize_unlock_tickets_task() { + ic_cdk::spawn(async { + let _guard = + match crate::guard::TimerLogicGuard::new(FINALIZE_UNLOCK_TICKET_NAME.to_string()) { + Some(guard) => guard, + None => return, + }; + finalize_flight_unlock_tickets().await; + }); +} + +pub fn submit_unlock_tickets_task() { + ic_cdk::spawn(async { + let _guard = + match crate::guard::TimerLogicGuard::new(SUBMIT_UNLOCK_TICKETS_NAME.to_string()) { + Some(guard) => guard, + None => return, + }; + send_tickets_to_bitcoin().await; + }); +} + +#[test] +pub fn show_txid_from_vec_u8_data() { + fn parse_hex_string(input: &str) -> Result, Box> { + let cleaned = input.replace("\\", "").replace(" ", ""); + let bytes: Result, _> = (0..cleaned.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&cleaned[i..i + 2], 16)) + .collect(); + + Ok(bytes?) + } + let txid = r"\a2\44\f5\b0\69\73\4e\5d\15\de\14\8d\50\d5\60\08\21\07\83\09\2a\8d\78\cf\31\a5\77\19\2a\ef\c5\2a"; + let v = parse_hex_string(&txid).unwrap(); + dbg!(&hex::DisplayHex::to_upper_hex_string(&v)); + dbg!(&txid.to_string()); +} diff --git a/customs/doge/src/doge/chainparams.rs b/customs/doge/src/doge/chainparams.rs new file mode 100644 index 00000000..e543da92 --- /dev/null +++ b/customs/doge/src/doge/chainparams.rs @@ -0,0 +1,91 @@ +#![allow(unused)] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct ChainParams { + pub chain_name: &'static str, + pub genesis_block: &'static str, + pub p2pkh_address_prefix: u8, + pub p2sh_address_prefix: u8, + pub pkey_prefix: u8, + pub bip32_privkey_prefix: u32, + pub bip32_pubkey_prefix: u32, + pub bip32_wif_privkey_prefix: &'static str, + pub bip32_wif_pubkey_prefix: &'static str, +} + +pub static DOGE_MAIN_NET_CHAIN: ChainParams = ChainParams { + chain_name: "main", + genesis_block: "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691", + p2pkh_address_prefix: 0x1e, // D + p2sh_address_prefix: 0x16, // 9 or A + pkey_prefix: 0x9e, // Q or 6 + bip32_privkey_prefix: 0x02fac398, // dgpv + bip32_pubkey_prefix: 0x02facafd, // dgub + bip32_wif_privkey_prefix: "dgpv", + bip32_wif_pubkey_prefix: "dgub", +}; + +pub static DOGE_TEST_NET_CHAIN: ChainParams = ChainParams { + chain_name: "test", + genesis_block: "bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e", + p2pkh_address_prefix: 0x71, // n + p2sh_address_prefix: 0xc4, // 2 + pkey_prefix: 0xf1, // 9 or c + bip32_privkey_prefix: 0x04358394, // tprv + bip32_pubkey_prefix: 0x043587cf, // tpub + bip32_wif_privkey_prefix: "tprv", + bip32_wif_pubkey_prefix: "tpub", +}; + +pub static DOGE_REG_TEST_CHAIN: ChainParams = ChainParams { + chain_name: "regtest", + genesis_block: "3d2160a3b5dc4a9d62e7e66a295f70313ac808440ef7400d6c0772171ce973a5", + p2pkh_address_prefix: 0x6f, // n + p2sh_address_prefix: 0xc4, // 2 + pkey_prefix: 0xef, // + bip32_privkey_prefix: 0x04358394, // tprv + bip32_pubkey_prefix: 0x043587cf, // tpub + bip32_wif_privkey_prefix: "tprv", + bip32_wif_pubkey_prefix: "tpub", +}; + +pub type KeyBits = u8; // keyECPriv,keyECPub,keyBip32Priv,keyBip32Pub,dogeMainNet,dogeTestNet + +pub const KEY_NONE: KeyBits = 0; +pub const KEY_ECPRIV: KeyBits = 1; +pub const KEY_ECPUB: KeyBits = 2; +pub const KEY_BIP32_PRIV: KeyBits = 4; +pub const KEY_BIP32_PUB: KeyBits = 8; +pub const MAIN_NET_DOGE: KeyBits = 16; +pub const TEST_NET_DOGE: KeyBits = 32; +pub const MAIN_NET_BTC: KeyBits = 64; + +pub fn chain_from_key_bits(key: KeyBits) -> &'static ChainParams { + if (key & MAIN_NET_DOGE) != 0 { + return &DOGE_MAIN_NET_CHAIN; + } + + &DOGE_TEST_NET_CHAIN // fallback +} + +pub fn key_bits_for_chain(chain: &ChainParams) -> KeyBits { + let mut bits = 0u8; + if chain == &DOGE_MAIN_NET_CHAIN { + bits |= MAIN_NET_DOGE + } else if chain == &DOGE_TEST_NET_CHAIN { + bits |= TEST_NET_DOGE + } + bits +} + +pub fn chain_from_wif(wif: &str) -> &'static ChainParams { + let wif = wif.as_bytes(); + if wif.is_empty() { + return &DOGE_TEST_NET_CHAIN; // fallback + } + match wif[0] { + b'D' | b'9' | b'A' | b'Q' | b'6' | b'd' => &DOGE_MAIN_NET_CHAIN, + _ => &DOGE_TEST_NET_CHAIN, + } +} diff --git a/customs/doge/src/doge/ecdsa.rs b/customs/doge/src/doge/ecdsa.rs new file mode 100644 index 00000000..230d7a3a --- /dev/null +++ b/customs/doge/src/doge/ecdsa.rs @@ -0,0 +1,42 @@ +use ic_cdk::api::management_canister::ecdsa; +use ic_crypto_extended_bip32::{DerivationIndex, DerivationPath, ExtendedBip32DerivationOutput}; + +pub type ECDSAPublicKey = ecdsa::EcdsaPublicKeyResponse; + +/// Returns a valid extended BIP-32 derivation path from an Account (Principal + subaccount) +pub fn derive_public_key( + ecdsa_public_key: &ECDSAPublicKey, + derivation_path: Vec>, +) -> ECDSAPublicKey { + let ExtendedBip32DerivationOutput { + derived_public_key, + derived_chain_code, + } = DerivationPath::new(derivation_path.into_iter().map(DerivationIndex).collect()) + .public_key_derivation(&ecdsa_public_key.public_key, &ecdsa_public_key.chain_code) + .expect("bug: failed to derive an ECDSA public key from valid inputs"); + ECDSAPublicKey { + public_key: derived_public_key, + chain_code: derived_chain_code, + } +} + +pub async fn sign_with( + key_name: &str, + derivation_path: Vec>, + message_hash: [u8; 32], +) -> Result, String> { + let args = ecdsa::SignWithEcdsaArgument { + message_hash: message_hash.to_vec(), + derivation_path, + key_id: ecdsa::EcdsaKeyId { + curve: ecdsa::EcdsaCurve::Secp256k1, + name: key_name.to_string(), + }, + }; + + let (response,): (ecdsa::SignWithEcdsaResponse,) = ecdsa::sign_with_ecdsa(args) + .await + .map_err(|err| format!("sign_with_ecdsa failed {:?}", err))?; + + Ok(response.signature) +} \ No newline at end of file diff --git a/customs/doge/src/doge/fee.rs b/customs/doge/src/doge/fee.rs new file mode 100644 index 00000000..a8e59dca --- /dev/null +++ b/customs/doge/src/doge/fee.rs @@ -0,0 +1,15 @@ +#![allow(unused)] +pub const DOGE_AMOUNT: u64 = 100_000_000; +pub const CENT_AMOUNT: u64 = 1_000_000; +// https://github.com/dogecoin/dogecoin/blob/master/doc/fee-recommendation.md +// 0.01 DOGE per kilobyte transaction fee +// 0.01 DOGE dust limit (discard threshold) +// 0.001 DOGE replace-by-fee increments +pub const MIN_FEE: u64 = 1_000_000; +pub const MIN_FEE_RATE: u64 = 1_000; // units per vByte +pub const DUST_LIMIT: u64 = 1_000_000; + +pub fn fee_by_size(bytes: u64, fee_rate: Option) -> u64 { + let fee_rate = fee_rate.unwrap_or(MIN_FEE_RATE).max(MIN_FEE_RATE); + (bytes * fee_rate).max(MIN_FEE) +} diff --git a/customs/doge/src/doge/mod.rs b/customs/doge/src/doge/mod.rs new file mode 100644 index 00000000..2f70359e --- /dev/null +++ b/customs/doge/src/doge/mod.rs @@ -0,0 +1,9 @@ +pub mod transaction; +pub mod script; +pub mod chainparams; +pub mod opcodes; +pub mod ecdsa; +pub mod rpc; +pub mod tatum_rpc; +pub mod fee; +pub mod sighash; \ No newline at end of file diff --git a/customs/doge/src/doge/opcodes.rs b/customs/doge/src/doge/opcodes.rs new file mode 100644 index 00000000..e727b811 --- /dev/null +++ b/customs/doge/src/doge/opcodes.rs @@ -0,0 +1,147 @@ +#![allow(unused)] + + +// https://github.com/dogecoin/dogecoin/blob/master/src/script/script.h + +/** Script opcodes */ +// push value +pub const OP_0: u8 = 0x00; +pub const OP_FALSE: u8 = OP_0; +pub const OP_PUSHDATA1: u8 = 0x4c; +pub const OP_PUSHDATA2: u8 = 0x4d; +pub const OP_PUSHDATA4: u8 = 0x4e; +pub const OP_1NEGATE: u8 = 0x4f; +pub const OP_RESERVED: u8 = 0x50; +pub const OP_1: u8 = 0x51; +pub const OP_TRUE: u8 = OP_1; +pub const OP_2: u8 = 0x52; +pub const OP_3: u8 = 0x53; +pub const OP_4: u8 = 0x54; +pub const OP_5: u8 = 0x55; +pub const OP_6: u8 = 0x56; +pub const OP_7: u8 = 0x57; +pub const OP_8: u8 = 0x58; +pub const OP_9: u8 = 0x59; +pub const OP_10: u8 = 0x5a; +pub const OP_11: u8 = 0x5b; +pub const OP_12: u8 = 0x5c; +pub const OP_13: u8 = 0x5d; +pub const OP_14: u8 = 0x5e; +pub const OP_15: u8 = 0x5f; +pub const OP_16: u8 = 0x60; + +// control +pub const OP_NOP: u8 = 0x61; +pub const OP_VER: u8 = 0x62; +pub const OP_IF: u8 = 0x63; +pub const OP_NOTIF: u8 = 0x64; +pub const OP_VERIF: u8 = 0x65; +pub const OP_VERNOTIF: u8 = 0x66; +pub const OP_ELSE: u8 = 0x67; +pub const OP_ENDIF: u8 = 0x68; +pub const OP_VERIFY: u8 = 0x69; +pub const OP_RETURN: u8 = 0x6a; + +// stack ops +pub const OP_TOALTSTACK: u8 = 0x6b; +pub const OP_FROMALTSTACK: u8 = 0x6c; +pub const OP_2DROP: u8 = 0x6d; +pub const OP_2DUP: u8 = 0x6e; +pub const OP_3DUP: u8 = 0x6f; +pub const OP_2OVER: u8 = 0x70; +pub const OP_2ROT: u8 = 0x71; +pub const OP_2SWAP: u8 = 0x72; +pub const OP_IFDUP: u8 = 0x73; +pub const OP_DEPTH: u8 = 0x74; +pub const OP_DROP: u8 = 0x75; +pub const OP_DUP: u8 = 0x76; +pub const OP_NIP: u8 = 0x77; +pub const OP_OVER: u8 = 0x78; +pub const OP_PICK: u8 = 0x79; +pub const OP_ROLL: u8 = 0x7a; +pub const OP_ROT: u8 = 0x7b; +pub const OP_SWAP: u8 = 0x7c; +pub const OP_TUCK: u8 = 0x7d; + +// splice ops +pub const OP_CAT: u8 = 0x7e; +pub const OP_SUBSTR: u8 = 0x7f; +pub const OP_LEFT: u8 = 0x80; +pub const OP_RIGHT: u8 = 0x81; +pub const OP_SIZE: u8 = 0x82; + +// bit logic +pub const OP_INVERT: u8 = 0x83; +pub const OP_AND: u8 = 0x84; +pub const OP_OR: u8 = 0x85; +pub const OP_XOR: u8 = 0x86; +pub const OP_EQUAL: u8 = 0x87; +pub const OP_EQUALVERIFY: u8 = 0x88; +pub const OP_RESERVED1: u8 = 0x89; +pub const OP_RESERVED2: u8 = 0x8a; + +// numeric +pub const OP_1ADD: u8 = 0x8b; +pub const OP_1SUB: u8 = 0x8c; +pub const OP_2MUL: u8 = 0x8d; +pub const OP_2DIV: u8 = 0x8e; +pub const OP_NEGATE: u8 = 0x8f; +pub const OP_ABS: u8 = 0x90; +pub const OP_NOT: u8 = 0x91; +pub const OP_0NOTEQUAL: u8 = 0x92; + +pub const OP_ADD: u8 = 0x93; +pub const OP_SUB: u8 = 0x94; +pub const OP_MUL: u8 = 0x95; +pub const OP_DIV: u8 = 0x96; +pub const OP_MOD: u8 = 0x97; +pub const OP_LSHIFT: u8 = 0x98; +pub const OP_RSHIFT: u8 = 0x99; + +pub const OP_BOOLAND: u8 = 0x9a; +pub const OP_BOOLOR: u8 = 0x9b; +pub const OP_NUMEQUAL: u8 = 0x9c; +pub const OP_NUMEQUALVERIFY: u8 = 0x9d; +pub const OP_NUMNOTEQUAL: u8 = 0x9e; +pub const OP_LESSTHAN: u8 = 0x9f; +pub const OP_GREATERTHAN: u8 = 0xa0; +pub const OP_LESSTHANOREQUAL: u8 = 0xa1; +pub const OP_GREATERTHANOREQUAL: u8 = 0xa2; +pub const OP_MIN: u8 = 0xa3; +pub const OP_MAX: u8 = 0xa4; + +pub const OP_WITHIN: u8 = 0xa5; + +// crypto +pub const OP_RIPEMD160: u8 = 0xa6; +pub const OP_SHA1: u8 = 0xa7; +pub const OP_SHA256: u8 = 0xa8; +pub const OP_HASH160: u8 = 0xa9; +pub const OP_HASH256: u8 = 0xaa; +pub const OP_CODESEPARATOR: u8 = 0xab; +pub const OP_CHECKSIG: u8 = 0xac; +pub const OP_CHECKSIGVERIFY: u8 = 0xad; +pub const OP_CHECKMULTISIG: u8 = 0xae; +pub const OP_CHECKMULTISIGVERIFY: u8 = 0xaf; + +// expansion +pub const OP_NOP1: u8 = 0xb0; +pub const OP_CHECKLOCKTIMEVERIFY: u8 = 0xb1; +pub const OP_NOP2: u8 = OP_CHECKLOCKTIMEVERIFY; +pub const OP_CHECKSEQUENCEVERIFY: u8 = 0xb2; +pub const OP_NOP3: u8 = OP_CHECKSEQUENCEVERIFY; +pub const OP_NOP4: u8 = 0xb3; +pub const OP_NOP5: u8 = 0xb4; +pub const OP_NOP6: u8 = 0xb5; +pub const OP_NOP7: u8 = 0xb6; +pub const OP_NOP8: u8 = 0xb7; +pub const OP_NOP9: u8 = 0xb8; +pub const OP_NOP10: u8 = 0xb9; + +// template matching params +pub const OP_SMALLINTEGER: u8 = 0xfa; +pub const OP_PUBKEYS: u8 = 0xfb; +pub const OP_PUBKEYHASH: u8 = 0xfd; +pub const OP_PUBKEY: u8 = 0xfe; + +pub const OP_INVALIDOPCODE: u8 = 0xff; diff --git a/customs/doge/src/doge/rpc.rs b/customs/doge/src/doge/rpc.rs new file mode 100644 index 00000000..3b224c0b --- /dev/null +++ b/customs/doge/src/doge/rpc.rs @@ -0,0 +1,192 @@ +use std::str::FromStr; + +use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpHeader, HttpMethod, TransformContext, TransformFunc}; +use omnity_types::ic_log::ERROR; +use serde_json::json; +use ic_canister_log::log; + +use crate::{constants::{KB, KB100}, errors::CustomsError, types::{deserialize_hex, http_request_with_retry, serialize_hex, wrap_to_customs_error, RpcConfig}}; + +use super::transaction::{DogeRpcResponse, RpcTxOut, Transaction, Txid}; + +pub const PROXY_URL: &str = "https://common-rpc-proxy-398338012986.us-central1.run.app"; +pub const IDEMPOTENCY_KEY: &str = "X-Idempotency"; +pub const FORWARD_RPC: &str = "X-Forward-Host"; + +#[derive(Clone, Debug)] +pub struct DogeRpc { + pub url: String, + pub api_key: Option, +} + +impl DogeRpc { + pub async fn get_raw_transaction( + &self, + txid: &str + ) -> Result { + let mut headers = vec![ + HttpHeader { + name: "Content-Type".to_string(), + value: "application/json".to_string(), + }, + ]; + if self.api_key.is_some() { + headers.push(HttpHeader { + name: "x-api-key".to_string(), + value: self.api_key.clone().unwrap(), + }); + } + let mut request = CanisterHttpRequestArgument { + url: self.url.clone(), + method: HttpMethod::POST, + body: Some(json!({ + "jsonrpc": "2.0", + "method": "getrawtransaction", + "params": [txid], + "id": 1 + }).to_string().into_bytes()), + max_response_bytes: Some(KB100), + transform: Some(TransformContext { + function: TransformFunc(candid::Func { + principal: ic_cdk::api::id(), + method: "transform".to_string(), + }), + context: vec![], + }), + headers + }; + self.proxy_request(&mut request); + let response = http_request_with_retry(request).await?; + let raw_tx: DogeRpcResponse = serde_json::from_slice(&response.body).map_err(|e| { + log!(ERROR, "json error {:?}", e); + CustomsError::RpcError( + "failed to decode transaction from json".to_string(), + ) + })?; + let result = raw_tx.unwrap_result()?; + + let tx: Transaction = deserialize_hex(&result).map_err(wrap_to_customs_error)?; + Ok(tx) + } + + pub async fn get_tx_out( + &self, + txid: &str, + ) -> Result { + let mut headers = vec![ + HttpHeader { + name: "Content-Type".to_string(), + value: "application/json".to_string(), + }, + + ]; + if self.api_key.is_some() { + headers.push(HttpHeader { + name: "x-api-key".to_string(), + value: self.api_key.clone().unwrap(), + }); + } + let mut request = CanisterHttpRequestArgument { + url: self.url.clone(), + method: HttpMethod::POST, + body: Some(json!({ + "jsonrpc": "2.0", + "method": "gettxout", + "params": [txid, 0], + "id": 1 + }).to_string().into_bytes()), + max_response_bytes: Some(KB100), + transform: Some(TransformContext { + function: TransformFunc(candid::Func { + principal: ic_cdk::api::id(), + method: "transform".to_string(), + }), + context: vec![], + }), + headers + }; + self.proxy_request(&mut request); + let response = http_request_with_retry(request).await?; + let tx_out_response: DogeRpcResponse = serde_json::from_slice(&response.body).map_err(|e| { + log!(ERROR, "json error {:?}", e); + CustomsError::RpcError( + "failed to decode transaction from json".to_string(), + ) + })?; + + let result = tx_out_response.unwrap_result()?; + + Ok(result) + } + + pub async fn send_transaction( + &self, + tx: &Transaction + )-> Result{ + let tx_hex = serialize_hex(tx); + let mut headers = vec![ + HttpHeader { + name: "Content-Type".to_string(), + value: "application/json".to_string(), + }, + ]; + if self.api_key.is_some() { + headers.push(HttpHeader { + name: "x-api-key".to_string(), + value: self.api_key.clone().unwrap(), + }); + } + let mut request = CanisterHttpRequestArgument { + url: self.url.clone(), + method: HttpMethod::POST, + body: Some(json!({ + "jsonrpc": "2.0", + "method": "sendrawtransaction", + "params": [tx_hex], + "id": 1 + }).to_string().into_bytes()), + max_response_bytes: Some(KB), + transform: Some(TransformContext { + function: TransformFunc(candid::Func { + principal: ic_cdk::api::id(), + method: "transform".to_string(), + }), + context: vec![], + }), + headers + }; + + self.proxy_request(&mut request); + let response = http_request_with_retry(request.clone()).await?; + let rpc_response: DogeRpcResponse = serde_json::from_slice(&response.body).map_err(|e| { + log!(ERROR, "json error {:?}", e); + CustomsError::RpcError( + "failed to desc result from json".to_string(), + ) + })?; + + let txid_str = rpc_response.unwrap_result()?; + + let txid = Txid::from_str(&txid_str).map_err(wrap_to_customs_error)?; + Ok(txid) + } + + fn proxy_request(&self, request: &mut CanisterHttpRequestArgument) { + request.url = PROXY_URL.to_string(); + let idempotency_key = format!("doge_customs-{}", ic_cdk::api::time()); + request.headers.push(HttpHeader { + name: IDEMPOTENCY_KEY.to_string(), + value: idempotency_key, + }); + request.headers.push(HttpHeader { + name: FORWARD_RPC.to_string(), + value: self.url.to_string(), + }); + } + +} + +pub async fn get_raw_transaction_by_rpc( txid: &str, rpc_config: RpcConfig) -> Result { + let doge_rpc = DogeRpc::from(rpc_config); + doge_rpc.get_raw_transaction(txid).await +} diff --git a/customs/doge/src/doge/script.rs b/customs/doge/src/doge/script.rs new file mode 100644 index 00000000..2f7d2146 --- /dev/null +++ b/customs/doge/src/doge/script.rs @@ -0,0 +1,207 @@ +#![allow(unused)] +use bitcoin::base58; +use bitcoin::hashes::{hash160, Hash}; +use std::str::FromStr; + +use crate::errors::CustomsError; + +use super::chainparams::ChainParams; +use super::opcodes::*; + +pub use bitcoin::key::PubkeyHash; +pub use bitcoin::script::{Bytes, PushBytes, Script, ScriptBuf, ScriptHash}; + +// Dogecoin Script Types enum. +// Inferred from ScriptPubKey scripts by pattern-matching the code (script templates) +// https://github.com/dogecoin/dogecoin/blob/master/src/script/standard.cpp#L24 +#[derive(Clone, PartialEq, Eq, Debug, Hash, Default)] +pub enum ScriptType { + #[default] + NonStandard, + PubKey, + PubKeyHash, + ScriptHash, + MultiSig, + NullData, + WitnessV0KeyHash, + WitnessV0ScriptHash, +} + +impl std::fmt::Display for ScriptType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ScriptType::NonStandard => write!(f, "nonstandard"), + ScriptType::PubKey => write!(f, "pubkey"), + ScriptType::PubKeyHash => write!(f, "pubkeyhash"), + ScriptType::ScriptHash => write!(f, "scripthash"), + ScriptType::MultiSig => write!(f, "multisig"), + ScriptType::NullData => write!(f, "nulldata"), + ScriptType::WitnessV0KeyHash => write!(f, "witness_v0_keyhash"), + ScriptType::WitnessV0ScriptHash => write!(f, "witness_v0_scripthash"), + } + } +} + +pub const ECPRIV_KEY_LEN: usize = 32; // bytes. +pub const ECPUB_KEY_COMPRESSED_LEN: usize = 33; // bytes: [x02/x03][32-X] 2=even 3=odd +pub const ECPUB_KEY_UNCOMPRESSED_LEN: usize = 65; // bytes: [x04][32-X][32-Y] + +#[derive(Clone, PartialEq, Eq, Debug, Hash, Default)] +pub struct Address(pub [u8; 21]); // Dogecoin address (base-58 Public Key Hash aka PKH) +impl Address { + pub fn is_p2pkh(&self, chain: &ChainParams) -> bool { + self.0[0] == chain.p2pkh_address_prefix + } + + pub fn is_p2sh(&self, chain: &ChainParams) -> bool { + self.0[0] == chain.p2sh_address_prefix + } + + pub fn is_valid(&self, chain: &ChainParams) -> bool { + self.0[0] == chain.p2pkh_address_prefix || self.0[0] == chain.p2sh_address_prefix + } + + pub fn to_script(&self, chain: &ChainParams) -> ScriptBuf { + if self.is_p2pkh(chain) { + ScriptBuf::new_p2pkh(&PubkeyHash::from_slice(&self.0[1..]).unwrap()) + } else if self.is_p2sh(chain) { + ScriptBuf::new_p2sh(&ScriptHash::from_slice(&self.0[1..]).unwrap()) + } else { + ScriptBuf::default() + } + } +} + +impl std::fmt::Display for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", base58::encode_check(&self.0)) + } +} + +impl FromStr for Address { + type Err = String; + + fn from_str(s: &str) -> Result { + match base58::decode_check(s) { + Ok(key) => { + let mut addr = [0u8; 21]; + if key.len() != 21 { + return Err("invalid address".to_string()); + } + + addr.copy_from_slice(&key); + Ok(Address(addr)) + } + Err(_) => Err("invalid address".to_string()), + } + } +} + +pub fn hash160_to_address(hash: &[u8], prefix: u8) -> Address { + assert!( + hash.len() == 20, + "hash160_to_address: wrong RIPEMD-160 length" + ); + let mut addr = Address::default(); + addr.0[0] = prefix; + addr.0[1..21].copy_from_slice(hash); + addr +} + +pub fn p2pkh_address(pubkey: &[u8], chain: &ChainParams) -> Result { + if !((pubkey.len() == ECPUB_KEY_UNCOMPRESSED_LEN && pubkey[0] == 0x04) + || (pubkey.len() == ECPUB_KEY_COMPRESSED_LEN && (pubkey[0] == 0x02 || pubkey[0] == 0x03))) + { + return Err(CustomsError::CustomError("p2pkh_address: bad pubkey length".to_string())); + } + let payload = hash160::Hash::hash(pubkey); + Ok(hash160_to_address( + payload.as_ref(), + chain.p2pkh_address_prefix, + )) +} + +pub fn p2sh_address(script: &[u8], chain: &ChainParams) -> Result { + if script.is_empty() { + return Err("p2sh_address: bad script length".to_string()); + } + + let payload = hash160::Hash::hash(script); + Ok(hash160_to_address( + payload.as_ref(), + chain.p2sh_address_prefix, + )) +} + +pub fn classify_script(script: &[u8], chain: &ChainParams) -> (ScriptType, Option
) { + let l = script.len(); + // P2PKH: OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG (25) + if l == 25 + && script[0] == OP_DUP + && script[1] == OP_HASH160 + && script[2] == 20 + && script[23] == OP_EQUALVERIFY + && script[24] == OP_CHECKSIG + { + let addr = hash160_to_address(&script[3..23], chain.p2pkh_address_prefix); + return (ScriptType::PubKeyHash, Some(addr)); + } + + // P2PK: OP_CHECKSIG + if l == 35 && script[0] == 33 && script[34] == OP_CHECKSIG { + // no Base58 Address for P2PK. + return (ScriptType::PubKey, None); + } + + // P2PK: OP_CHECKSIG + if l == 67 && script[0] == 65 && script[66] == OP_CHECKSIG { + // no Base58 Address for P2PK. + return (ScriptType::PubKey, None); + } + + // P2SH: OP_HASH160 0x14 OP_EQUAL + if l == 23 && script[0] == OP_HASH160 && script[1] == 20 && script[22] == OP_EQUAL { + let addr = hash160_to_address(&script[2..22], chain.p2sh_address_prefix); + return (ScriptType::ScriptHash, Some(addr)); + } + + // OP_m OP_n OP_CHECKMULTISIG + if l >= 3 + 34 + && script[l - 1] == OP_CHECKMULTISIG + && is_op_n1(script[l - 2]) + && is_op_n1(script[0]) + { + let mut num_keys = script[l - 2] - (OP_1 - 1); + let mut ofs = 1; + let end_keys = l - 2; + while ofs < end_keys && num_keys > 0 { + if script[ofs] == 65 && ofs + 66 <= end_keys { + // no Base58 Address for PubKey. + ofs += 66 + } else if script[ofs] == 33 && ofs + 34 <= end_keys { + // no Base58 Address for PubKey. + ofs += 34 + } else { + break; + } + num_keys -= 1 + } + + if ofs == end_keys && num_keys == 0 { + return (ScriptType::MultiSig, None); + } + + return (ScriptType::NonStandard, None); + } + + // OP_RETURN + if l > 0 && script[0] == OP_RETURN { + return (ScriptType::NullData, None); + } + + (ScriptType::NonStandard, None) +} + +fn is_op_n1(op: u8) -> bool { + (OP_1..=OP_16).contains(&op) +} diff --git a/customs/doge/src/doge/sighash.rs b/customs/doge/src/doge/sighash.rs new file mode 100644 index 00000000..5f82c99b --- /dev/null +++ b/customs/doge/src/doge/sighash.rs @@ -0,0 +1,217 @@ +#![allow(unused)] + +use bitcoin::consensus::Encodable; +use bitcoin::hashes::{hash_newtype, sha256d, Hash}; +use bitcoin_io::Write; +use std::borrow::{Borrow, BorrowMut}; +use std::ops::Deref; + +pub use bitcoin::ecdsa::Signature as SighashSignature; +pub use bitcoin::secp256k1::{Message, PublicKey}; +pub use bitcoin::EcdsaSighashType; + +use crate::doge::script::ScriptBuf; + +use super::transaction::*; + +hash_newtype! { + /// Hash of a transaction according to the legacy signature algorithm. + #[hash_newtype(forward)] + pub struct Sighash(sha256d::Hash); +} + +impl Deref for Sighash { + type Target = [u8; 32]; + + fn deref(&self) -> &[u8; 32] { + self.0.as_byte_array() + } +} + +impl From for Message { + fn from(hash: Sighash) -> Self { + Message::from_digest(hash.to_byte_array()) + } +} + +/// Used for signature hash for invalid use of SIGHASH_SINGLE. +pub(crate) const UINT256_ONE: [u8; 32] = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +/// Efficiently calculates signature hash message for legacy, segwit and taproot inputs. +#[derive(Debug)] +pub struct SighashCache> { + tx: T, +} + +impl> SighashCache { + /// Constructs a new `SighashCache` from an unsigned transaction. + pub fn new(tx: R) -> Self { + SighashCache { tx } + } + + /// Returns the reference to the cached transaction. + pub fn transaction(&self) -> &Transaction { + self.tx.borrow() + } + + /// Destroys the cache and recovers the stored transaction. + pub fn into_transaction(self) -> R { + self.tx + } + + pub fn encode_signing_data_to( + &self, + writer: &mut W, + input_index: usize, + script_pubkey: &ScriptBuf, + sighash_type: EcdsaSighashType, + ) -> Result { + // Validate input_index. + if input_index >= self.tx.borrow().input.len() { + return Err(format!("input index {} out of range", input_index)); + } + + if is_invalid_use_of_sighash_single( + sighash_type, + input_index, + self.tx.borrow().output.len(), + ) { + // We cannot correctly handle the SIGHASH_SINGLE bug here because usage of this function + // will result in the data written to the writer being hashed, however the correct + // handling of the SIGHASH_SINGLE bug is to return the 'one array' - either implement + // this behaviour manually or use `signature_hash()`. + return Ok(false); + } + + let (sighash, anyone_can_pay) = split_anyonecanpay_flag(sighash_type); + let stx = self.tx.borrow(); + // Build tx to sign + let mut tx = Transaction { + version: stx.version, + lock_time: stx.lock_time, + input: vec![], + output: vec![], + }; + // Add all inputs necessary.. + if anyone_can_pay { + tx.input = vec![TxIn { + prevout: stx.input[input_index].prevout, + script: script_pubkey.clone(), + sequence: stx.input[input_index].sequence, + witness: Witness::default(), + }]; + } else { + tx.input = Vec::with_capacity(stx.input.len()); + for (n, input) in stx.input.iter().enumerate() { + tx.input.push(TxIn { + prevout: input.prevout, + script: if n == input_index { + script_pubkey.clone() + } else { + ScriptBuf::new() + }, + sequence: if n != input_index + && (sighash == EcdsaSighashType::Single + || sighash == EcdsaSighashType::None) + { + 0 + } else { + input.sequence + }, + witness: Witness::default(), + }); + } + } + // ..then all outputs + tx.output = match sighash { + EcdsaSighashType::All => stx.output.clone(), + EcdsaSighashType::Single => { + let output_iter = stx + .output + .iter() + .take(input_index + 1) // sign all outputs up to and including this one, but erase + .enumerate() // all of them except for this one + .map(|(n, out)| { + if n == input_index { + out.clone() + } else { + TxOut::default() + } + }); + output_iter.collect() + } + EcdsaSighashType::None => vec![], + _ => unreachable!(), + }; + // hash the result + tx.consensus_encode(writer).map_err(|err| err.to_string())?; + sighash_type + .to_u32() + .to_le_bytes() + .consensus_encode(writer) + .map_err(|err| err.to_string())?; + Ok(true) + } + + /// Computes a signature hash for a given input index with a given sighash flag. + pub fn signature_hash( + &self, + input_index: usize, + script_pubkey: &ScriptBuf, + sighash_type: EcdsaSighashType, + ) -> Result { + let mut engine = Sighash::engine(); + match self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_type) { + Ok(true) => Ok(Sighash::from_engine(engine)), + Ok(false) => Ok(Sighash::from_byte_array(UINT256_ONE)), + Err(e) => Err(e), + } + } +} + +impl> SighashCache { + /// Allows modification of script. + /// + /// This method allows doing exactly that if the transaction is owned by the `SighashCache` or + /// borrowed mutably. + pub fn set_input_script( + &mut self, + input_index: usize, + signature: &SighashSignature, + pubkey: &PublicKey, + ) -> Result<(), String> { + self.tx + .borrow_mut() + .input + .get_mut(input_index) + .map(|i| { + let mut buf = ScriptBuf::new(); + buf.push_slice(signature.serialize()); + buf.push_slice(pubkey.serialize()); + i.script = buf; + }) + .ok_or("input index out of range".to_string()) + } +} + +fn split_anyonecanpay_flag(st: EcdsaSighashType) -> (EcdsaSighashType, bool) { + use EcdsaSighashType::*; + match st { + All => (All, false), + None => (None, false), + Single => (Single, false), + AllPlusAnyoneCanPay => (All, true), + NonePlusAnyoneCanPay => (None, true), + SinglePlusAnyoneCanPay => (Single, true), + } +} + +fn is_invalid_use_of_sighash_single( + ty: EcdsaSighashType, + input_index: usize, + outputs_len: usize, +) -> bool { + ty == EcdsaSighashType::Single && input_index >= outputs_len +} \ No newline at end of file diff --git a/customs/doge/src/doge/tatum_rpc.rs b/customs/doge/src/doge/tatum_rpc.rs new file mode 100644 index 00000000..961229a8 --- /dev/null +++ b/customs/doge/src/doge/tatum_rpc.rs @@ -0,0 +1,327 @@ +use std::str::FromStr; + +use crate::{constants::KB100, errors::CustomsError, types::http_request_with_retry}; +use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpHeader, HttpMethod, TransformContext, TransformFunc}; +use serde::{Deserialize, Serialize}; + +use super::{rpc::DogeRpc, transaction::Txid}; + + +pub struct TatumDogeRpc { + doge_rpc: DogeRpc +} + +impl TatumDogeRpc { + pub fn new(tatum_rpc_url: String, tatum_api_key: Option) -> Self { + let doge_rpc = DogeRpc { + url: tatum_rpc_url, + api_key: tatum_api_key, + }; + Self { + doge_rpc + } + } + + pub async fn get_transactions_by_address(&self, address: String )-> Result, CustomsError>{ + let mut headers = vec![ + HttpHeader { + name: "Content-Type".to_string(), + value: "application/json".to_string(), + }, + ]; + + if let Some(api_key) = self.doge_rpc.api_key.clone() { + headers.push(HttpHeader { + name: "x-api-key".to_string(), + value: api_key, + }); + }; + + let full_url = format!("{}/v3/dogecoin/transaction/address/{}?pageSize=3&txType=incoming", self.doge_rpc.url, address); + let request = CanisterHttpRequestArgument { + url: full_url, + method: HttpMethod::GET, + body: None, + max_response_bytes: Some(KB100), + transform: Some(TransformContext { + function: TransformFunc(candid::Func { + principal: ic_cdk::api::id(), + method: "transform".to_string(), + }), + context: vec![], + }), + headers + }; + + let response = http_request_with_retry(request.clone()).await?; + + let rpc_response: Vec = serde_json::from_slice(&response.body).map_err(|_| { + CustomsError::RpcError( + "failed to desc result from json".to_string(), + ) + })?; + + let mut txids = vec![]; + for e in rpc_response.iter() { + txids.push(Txid::from_str(e.hash.as_str()).map_err( + |e| CustomsError::RpcError( + format!("failed to parse txid: {:?}", e) + ) + )?); + } + + Ok(txids) + } +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct Transaction { + // pub version: u32, + // pub lock_time: u32, + // pub input: Vec, + pub hash: String, + // pub outputs: Vec, +} + +#[test] +pub fn test() { + + let r = r#" + [ + { + "blockNumber": 5535077, + "fee": "0.373", + "hash": "2a68d0319985d7a35eec7b97ce707f3a5b2872e173bb8e9dd21890fdccd0c172", + "hex": "02000000023d1a3ac89095b058f7cf1ab6df64a8d34ce19289b04b438a892bf5a004350114010000006b48304502210099f5ee230866637643700a94bac827df7bc03f8528613d94697c459aef1bb9f602201763b28fe4f0702af69e683f21ad3da82df01e1a6b16a1394bba661664b624cf0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026ffffffff43b82a5d4eeff0db2eaa3db7350cf45b55ba5d64f56ae63c77c5e5b63b2bbfae000000006a47304402203cf939c7d546ad6a2b9bf1c6674e14d13e2a5a7a83ad83ffb0f0ffe7c5eed31c02204a42edfbf27a0faac30f1c04eee881d20edb98174ad06558fbe914b733529a220121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026ffffffff0200e1f505000000001976a91457996f3bd447eb254ed59e832a0aceedf842497f88aca0a28eb9010000001976a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac00000000", + "index": 8, + "inputs": [ + { + "prevout": { + "hash": "14013504a0f52b898a434bb08992e14cd3a864dfb61acff758b09590c83a1a3d", + "index": 1 + }, + "sequence": 4294967295, + "script": "48304502210099f5ee230866637643700a94bac827df7bc03f8528613d94697c459aef1bb9f602201763b28fe4f0702af69e683f21ad3da82df01e1a6b16a1394bba661664b624cf0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026", + "coin": { + "version": 2, + "height": 5534613, + "value": "74.454", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "type": null, + "reqSigs": null, + "coinbase": false + } + }, + { + "prevout": { + "hash": "aebf2b3bb6e5c5773ce66af5645dba555bf40c35b73daa2edbf0ef4e5d2ab843", + "index": 0 + }, + "sequence": 4294967295, + "script": "47304402203cf939c7d546ad6a2b9bf1c6674e14d13e2a5a7a83ad83ffb0f0ffe7c5eed31c02204a42edfbf27a0faac30f1c04eee881d20edb98174ad06558fbe914b733529a220121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026", + "coin": { + "version": 1, + "height": 5534995, + "value": "1", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "type": null, + "reqSigs": null, + "coinbase": false + } + } + ], + "locktime": 0, + "outputs": [ + { + "value": "1", + "script": "76a91457996f3bd447eb254ed59e832a0aceedf842497f88ac", + "address": "DD8H8mGhwhztaT6431VA58gEMu8vacL8Y7", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + }, + { + "value": "74.081", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + } + ], + "size": 373, + "time": 1736246223, + "version": 2, + "vsize": 373, + "witnessHash": "2a68d0319985d7a35eec7b97ce707f3a5b2872e173bb8e9dd21890fdccd0c172" + }, + { + "blockNumber": 5534613, + "fee": "1.125", + "hash": "14013504a0f52b898a434bb08992e14cd3a864dfb61acff758b09590c83a1a3d", + "hex": "02000000018073e3a7567120b22a6b37010b7ec50d1cd3b1413a2b5a0429d029e806f7d1f4010000006b483045022100fea54ebe0e61b121d6056c88521d8b1c8313f5758adfa5de9a873f738382997402207549dadb5761bb64a76e826e2f25a1a29f227d73d30e31cdef5e1c443cd63c210121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026ffffffff0200e1f505000000001976a91457996f3bd447eb254ed59e832a0aceedf842497f88acc0c9c7bb010000001976a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac00000000", + "index": 57, + "inputs": [ + { + "prevout": { + "hash": "f4d1f706e829d029045a2b3a41b1d31c0dc57e0b01376b2ab2207156a7e37380", + "index": 1 + }, + "sequence": 4294967295, + "script": "483045022100fea54ebe0e61b121d6056c88521d8b1c8313f5758adfa5de9a873f738382997402207549dadb5761bb64a76e826e2f25a1a29f227d73d30e31cdef5e1c443cd63c210121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026", + "coin": { + "version": 2, + "height": 5534606, + "value": "76.579", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "type": null, + "reqSigs": null, + "coinbase": false + } + } + ], + "locktime": 0, + "outputs": [ + { + "value": "1", + "script": "76a91457996f3bd447eb254ed59e832a0aceedf842497f88ac", + "address": "DD8H8mGhwhztaT6431VA58gEMu8vacL8Y7", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + }, + { + "value": "74.454", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + } + ], + "size": 226, + "time": 1736215756, + "version": 2, + "vsize": 226, + "witnessHash": "14013504a0f52b898a434bb08992e14cd3a864dfb61acff758b09590c83a1a3d" + }, + { + "blockNumber": 5526416, + "fee": "0.225", + "hash": "508a6242603d54f63ed8e17553836cd66c6f019ebdf5f85d4acd4b5a3d56d354", + "hex": "0200000001e8ee6ba4aedaabbce0d3431ca7ec1450a928aad27b5f61fd73119c5ac1f99467010000006a4730440220254a3b496005bf0dcca9bfbb792191490e126cd146decae2c3b36e47ebc0391202206218cef80b975c21779ee9387ba6b16cabc03b462103425010b525aeb3b882ec0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026ffffffff0200c2eb0b000000001976a91457996f3bd447eb254ed59e832a0aceedf842497f88acc0554e03020000001976a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac00000000", + "index": 2, + "inputs": [ + { + "prevout": { + "hash": "6794f9c15a9c1173fd615f7bd2aa28a95014eca71c43d3e0bcabdaaea46beee8", + "index": 1 + }, + "sequence": 4294967295, + "script": "4730440220254a3b496005bf0dcca9bfbb792191490e126cd146decae2c3b36e47ebc0391202206218cef80b975c21779ee9387ba6b16cabc03b462103425010b525aeb3b882ec0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026", + "coin": { + "version": 2, + "height": 5525087, + "value": "88.679", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "type": null, + "reqSigs": null, + "coinbase": false + } + } + ], + "locktime": 0, + "outputs": [ + { + "value": "2", + "script": "76a91457996f3bd447eb254ed59e832a0aceedf842497f88ac", + "address": "DD8H8mGhwhztaT6431VA58gEMu8vacL8Y7", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + }, + { + "value": "86.454", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + } + ], + "size": 225, + "time": 1735698111, + "version": 2, + "vsize": 225, + "witnessHash": "508a6242603d54f63ed8e17553836cd66c6f019ebdf5f85d4acd4b5a3d56d354" + }, + { + "blockNumber": 5525087, + "fee": "0.225", + "hash": "6794f9c15a9c1173fd615f7bd2aa28a95014eca71c43d3e0bcabdaaea46beee8", + "hex": "0200000001896cd225c2809b94c3a00adcd083425c46f4a05e0b91606ccb669081c930c2b5010000006946304302201c99c97d8f7a1ca20c0669a2e0bcd2c957e6a863c57705a417c1d7c7d753b1bf021f1239739b6a8fecf450e6fc0f610752d707a67e1fe1c0924caf9b650abf800f0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026ffffffff0200c2eb0b000000001976a91457996f3bd447eb254ed59e832a0aceedf842497f88ac606a9110020000001976a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac00000000", + "index": 58, + "inputs": [ + { + "prevout": { + "hash": "b5c230c9819066cb6c60910b5ea0f4465c4283d0dc0aa0c3949b80c225d26c89", + "index": 1 + }, + "sequence": 4294967295, + "script": "46304302201c99c97d8f7a1ca20c0669a2e0bcd2c957e6a863c57705a417c1d7c7d753b1bf021f1239739b6a8fecf450e6fc0f610752d707a67e1fe1c0924caf9b650abf800f0121020c191f5ab73a4e5694240a58841f8295438f052888e8b86d9c26a206b58e7026", + "coin": { + "version": 2, + "height": 5525048, + "value": "90.904", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "type": null, + "reqSigs": null, + "coinbase": false + } + } + ], + "locktime": 0, + "outputs": [ + { + "value": "2", + "script": "76a91457996f3bd447eb254ed59e832a0aceedf842497f88ac", + "address": "DD8H8mGhwhztaT6431VA58gEMu8vacL8Y7", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + }, + { + "value": "88.679", + "script": "76a9143131fbf0980ccc6429e6b67b8a642d4e2b33db7588ac", + "address": "D9dDXck2s276Kyi4DRRhBuWojJKNanjiEW", + "scriptPubKey": { + "type": "pubkeyhash", + "reqSigs": 1 + } + } + ], + "size": 224, + "time": 1735613142, + "version": 2, + "vsize": 224, + "witnessHash": "6794f9c15a9c1173fd615f7bd2aa28a95014eca71c43d3e0bcabdaaea46beee8" + } +] + "#; + + let txs: Vec = serde_json::from_str(r).unwrap(); + dbg!(&txs); + +} \ No newline at end of file diff --git a/customs/doge/src/doge/transaction.rs b/customs/doge/src/doge/transaction.rs new file mode 100644 index 00000000..035645f4 --- /dev/null +++ b/customs/doge/src/doge/transaction.rs @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Dogecoin transactions. + +use bitcoin::consensus::{encode, Decodable, Encodable}; +use bitcoin::hashes::{hash_newtype, sha256d, Hash}; +use bitcoin::{ScriptBuf, VarInt}; +use bitcoin_io::{BufRead, Error, Write}; +use serde::{Deserialize, Serialize}; +use core::cmp; +use std::ops::Deref; + +use crate::errors::CustomsError; + +use super::chainparams::DOGE_MAIN_NET_CHAIN; +use super::script::classify_script; + +pub fn consensus_encode_vec(vv: &[T], w: &mut W) -> Result +where + T: Encodable, + W: Write + ?Sized, +{ + let mut len = 0; + VarInt::from(vv.len()).consensus_encode(w)?; + for v in vv.iter() { + len += v.consensus_encode(w)?; + } + Ok(len) +} + +pub fn consensus_decode_from_vec(r: &mut R) -> Result, encode::Error> +where + T: Decodable, + R: BufRead + ?Sized, +{ + let cap: VarInt = Decodable::consensus_decode(r)?; + let cap = cap.0 as usize; + let mut vv = Vec::with_capacity(cap); + for _ in 0..cap { + vv.push(Decodable::consensus_decode_from_finite_reader(r)?); + } + Ok(vv) +} + +pub fn err_string(err: impl std::fmt::Display) -> String { + err.to_string() +} + +hash_newtype! { + pub struct Txid(sha256d::Hash); +} + + +impl Default for Txid { + fn default() -> Txid { + Txid(sha256d::Hash::all_zeros()) + } +} + +impl Deref for Txid { + type Target = [u8; 32]; + + fn deref(&self) -> &[u8; 32] { + self.0.as_byte_array() + } +} + +impl Encodable for Txid { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.0.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for Txid { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + let hash: sha256d::Hash = Decodable::consensus_decode_from_finite_reader(r)?; + Ok(Txid(hash)) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct OutPoint { + /// The referenced transaction's txid. + pub txid: Txid, + /// The index of the referenced output in its transaction's vout. + pub vout: u32, +} + +impl OutPoint { + pub const SIZE: usize = 36; + pub fn is_null(&self) -> bool { + self.vout == u32::MAX && self.txid == Txid::default() + } +} + +impl Default for OutPoint { + fn default() -> OutPoint { + OutPoint { + txid: Txid::default(), + vout: u32::MAX, + } + } +} + +impl Encodable for OutPoint { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.txid.consensus_encode(w)?; + len += self.vout.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for OutPoint { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(OutPoint { + txid: Decodable::consensus_decode_from_finite_reader(r)?, + vout: Decodable::consensus_decode_from_finite_reader(r)?, + }) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default, Serialize, Deserialize)] +pub struct Witness { + pub stack: Vec, +} + +impl Encodable for Witness { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.stack.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for Witness { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(Witness { + stack: Decodable::consensus_decode_from_finite_reader(r)?, + }) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct TxIn { + pub prevout: OutPoint, + pub script: ScriptBuf, + pub sequence: u32, + pub witness: Witness, // Only Serialize & Deserialize through Transaction +} + +impl TxIn { + pub const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31; + pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22; + pub const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff; + pub const SEQUENCE_LOCKTIME_GRANULARITY: u32 = 9; + + pub fn with_outpoint(prevout: OutPoint) -> TxIn { + TxIn { + prevout, + script: ScriptBuf::new(), + sequence: u32::MAX, + witness: Witness::default(), + } + } + + /// Returns the base size of this input. + /// + /// Base size excludes the witness data (see [`Self::total_size`]). + pub fn size(&self) -> usize { + let mut size = OutPoint::SIZE; + + // 106 is the common size of a scriptSig + // if script is empty, we set the size to 106 to compute the fee size + let len = self.script.len(); + size += VarInt::from(len).size(); + size += len; + + size + 4 // Sequence::SIZE + } + + /// Returns the estimate number of bytes that this input contributes to a transaction. + pub fn estimate_size(&self) -> usize { + let mut size = OutPoint::SIZE; + + // 106 is the common size of a scriptSig + // if script is empty, we set the size to 106 to compute the fee size + let len = self.script.len().max(106); + size += VarInt::from(len).size(); + size += len; + size += 4; // Sequence::SIZE + size + self.witness.stack.len() + } +} + +impl Default for TxIn { + fn default() -> TxIn { + TxIn { + prevout: OutPoint::default(), + script: ScriptBuf::new(), + sequence: u32::MAX, + witness: Witness::default(), + } + } +} + +impl TryFrom<&[u8]> for TxIn { + type Error = String; + + fn try_from(data: &[u8]) -> Result { + let mut rd = data; + Self::consensus_decode_from_finite_reader(&mut rd).map_err(err_string) + } +} + +impl Encodable for TxIn { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.prevout.consensus_encode(w)?; + len += self.script.consensus_encode(w)?; + len += self.sequence.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for TxIn { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(TxIn { + prevout: Decodable::consensus_decode_from_finite_reader(r)?, + script: Decodable::consensus_decode_from_finite_reader(r)?, + sequence: Decodable::consensus_decode_from_finite_reader(r)?, + witness: Witness::default(), + }) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct TxOut { + pub value: u64, + pub script_pubkey: ScriptBuf, +} + +impl TxOut { + /// Returns the total number of bytes that this output contributes to a transaction. + pub fn size(&self) -> usize { + let len = self.script_pubkey.len(); + VarInt::from(len).size() + len + 8 // value size + } + + pub fn estimate_size(&self) -> usize { + self.size() + } + + pub fn to_bytes(&self) -> Vec { + let mut buf = Vec::new(); + self.consensus_encode(&mut buf).unwrap(); + buf + } + + pub fn get_mainnet_address(&self) -> Option { + let (_, addr_opt) = classify_script(&self.script_pubkey.as_bytes(), &DOGE_MAIN_NET_CHAIN); + addr_opt.map(|addr| addr.to_string()) + } +} + +impl Default for TxOut { + fn default() -> TxOut { + TxOut { + value: u64::MAX, + script_pubkey: ScriptBuf::new(), + } + } +} + +impl TryFrom<&[u8]> for TxOut { + type Error = String; + + fn try_from(data: &[u8]) -> Result { + let mut rd = data; + Self::consensus_decode_from_finite_reader(&mut rd).map_err(err_string) + } +} + +impl Encodable for TxOut { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.value.consensus_encode(w)?; + len += self.script_pubkey.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for TxOut { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(TxOut { + value: Decodable::consensus_decode_from_finite_reader(r)?, + script_pubkey: Decodable::consensus_decode_from_finite_reader(r)?, + }) + } +} + +/** + * Basic transaction serialization format: + * - int32_t nVersion + * - std::vector vin + * - std::vector vout + * - uint32_t nLockTime + * + * TODO: Extended transaction serialization format: + * - int32_t nVersion + * - unsigned char dummy = 0x00 + * - unsigned char flags (!= 0) + * - std::vector vin + * - std::vector vout + * - if (flags & 1): + * - CTxWitness wit; + * - uint32_t nLockTime + */ +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct Transaction { + pub version: u32, + pub lock_time: u32, + pub input: Vec, + pub output: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RawTransaction { + pub result: String, + pub error: Option, + pub id: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RpcTxOut { + pub bestblock: String, + pub confirmations: u32, + pub value: f64, + pub version: u32, + pub coinbase: bool, +} + + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct DogeRpcResponse { + pub result: T, + pub error: Option, + pub id: u32, +} + +impl DogeRpcResponse { + pub fn unwrap_result(self) -> Result { + self.error + .map_or(Ok(self.result), |e| Err(CustomsError::RpcError(e))) + } + +} + +impl cmp::PartialOrd for Transaction { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl cmp::Ord for Transaction { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.version + .cmp(&other.version) + .then(self.lock_time.cmp(&other.lock_time)) + .then(self.input.cmp(&other.input)) + .then(self.output.cmp(&other.output)) + } +} + +impl Transaction { + pub const CURRENT_VERSION: u32 = 1; + pub const MAX_STANDARD_VERSION: u32 = 2; + pub const SERIALIZE_TRANSACTION_NO_WITNESS: u32 = 0x40000000; + + /// Computes the [`Txid`]. + pub fn compute_txid(&self) -> Txid { + let mut enc = Txid::engine(); + self.version + .consensus_encode(&mut enc) + .expect("engines don't error"); + consensus_encode_vec(&self.input, &mut enc).expect("engines don't error"); + consensus_encode_vec(&self.output, &mut enc).expect("engines don't error"); + self.lock_time + .consensus_encode(&mut enc) + .expect("engines don't error"); + Txid::from_engine(enc) + } + + pub fn is_coinbase(&self) -> bool { + self.input.len() == 1 && self.input[0].prevout.is_null() + } + + /// Returns the base transaction size. + /// + /// > Base transaction size is the size of the transaction serialised with the witness data stripped. + pub fn size(&self) -> usize { + let mut size: usize = 4; // Serialized length of a u32 for the version number. + + size += VarInt::from(self.input.len()).size(); + size += self.input.iter().map(|input| input.size()).sum::(); + + size += VarInt::from(self.output.len()).size(); + size += self + .output + .iter() + .map(|output| output.size()) + .sum::(); + + size + 4 // LockTime::SIZE + } + + pub fn estimate_size(&self) -> usize { + let mut size: usize = 4; // Serialized length of a u32 for the version number. + + size += VarInt::from(self.input.len()).size(); + size += self + .input + .iter() + .map(|input| input.estimate_size()) + .sum::(); + + size += VarInt::from(self.output.len()).size(); + size += self + .output + .iter() + .map(|output| output.estimate_size()) + .sum::(); + + size + 4 // LockTime::SIZE + } + + pub fn to_bytes(&self) -> Vec { + let mut buf = Vec::new(); + self.consensus_encode(&mut buf).unwrap(); + buf + } +} + +impl Encodable for Transaction { + fn consensus_encode(&self, w: &mut W) -> Result { + let mut len = 0; + len += self.version.consensus_encode(w)?; + len += consensus_encode_vec(&self.input, w)?; + len += consensus_encode_vec(&self.output, w)?; + len += self.lock_time.consensus_encode(w)?; + Ok(len) + } +} + +impl Decodable for Transaction { + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(Transaction { + version: Decodable::consensus_decode_from_finite_reader(r)?, + input: consensus_decode_from_vec(r)?, + output: consensus_decode_from_vec(r)?, + lock_time: Decodable::consensus_decode_from_finite_reader(r)?, + }) + } +} + +impl TryFrom<&[u8]> for Transaction { + type Error = String; + + fn try_from(data: &[u8]) -> Result { + let mut rd = data; + Self::consensus_decode_from_finite_reader(&mut rd).map_err(err_string) + } +} + +impl From for Txid { + fn from(tx: Transaction) -> Txid { + tx.compute_txid() + } +} + +impl From<&Transaction> for Txid { + fn from(tx: &Transaction) -> Txid { + tx.compute_txid() + } +} \ No newline at end of file diff --git a/customs/doge/src/dogeoin_to_custom.rs b/customs/doge/src/dogeoin_to_custom.rs new file mode 100644 index 00000000..10eacf21 --- /dev/null +++ b/customs/doge/src/dogeoin_to_custom.rs @@ -0,0 +1,223 @@ +use std::str::FromStr; + +use crate::constants::FINALIZE_LOCK_TICKET_NAME; +use crate::doge::chainparams::DOGE_MAIN_NET_CHAIN; +use crate::doge::rpc::DogeRpc; +use crate::doge::script::classify_script; +use crate::doge::transaction::Transaction; +use crate::errors::CustomsError; +use crate::generate_ticket::GenerateTicketWithTxidArgs; +use crate::hub; +use crate::state::{finalization_time_estimate, mutate_state, read_state}; +use crate::types::{Destination, LockTicketRequest, Txid, Utxo}; +use ic_canister_log::log; +use omnity_types::ic_log::{ERROR, INFO}; + +pub async fn query_and_save_utxo_for_payment_address(txid: String) -> Result { + if read_state(|s| s.deposit_fee_tx_set.get(&txid).is_some()) { + Err(CustomsError::CustomError("already saved".to_string()))?; + } + + let doge_rpc: DogeRpc = read_state(|s| s.default_doge_rpc_config.clone()).into(); + let transaction = doge_rpc.get_raw_transaction(&txid).await?; + let (fee_payment_address, _) = + read_state(|s| s.get_address(Destination::fee_payment_address()))?; + let typed_txid = Txid::from_str(&txid).map_err(|_| CustomsError::InvalidTxId)?; + let mut total = 0; + for (i, out) in transaction.output.iter().enumerate() { + let receiver = classify_script(out.script_pubkey.as_bytes(), &DOGE_MAIN_NET_CHAIN) + .1 + .ok_or(CustomsError::CustomError( + "failed to get receiver from output".to_string(), + ))?; + if receiver.eq(&fee_payment_address) { + total += out.value; + mutate_state(|s| { + s.fee_payment_utxo.push(Utxo { + txid: typed_txid.clone(), + vout: i as u32, + value: out.value, + }); + }) + } + } + + if total > 0 { + mutate_state(|s| { + s.deposit_fee_tx_set.insert(txid, ()); + }) + } + + Ok(total) +} + +pub async fn check_transaction( + req: GenerateTicketWithTxidArgs, +) -> Result<(Transaction, u64, String), CustomsError> { + read_state(|s| s.tokens.get(&req.token_id).cloned()).ok_or(CustomsError::InvalidArgs( + serde_json::to_string(&req).unwrap(), + ))?; + read_state(|s| s.counterparties.get(&req.target_chain_id).cloned()).ok_or( + CustomsError::InvalidArgs(serde_json::to_string(&req).unwrap()), + )?; + + // if let Some() = read_state(|s| s.multi_rpc_config) + let default_doge_rpc: DogeRpc = read_state(|s| s.default_doge_rpc_config.clone()).into(); + let multi_rpc_config = read_state(|s| s.multi_rpc_config.clone()); + let transaction = if multi_rpc_config.rpc_list.len() > 0 { + multi_rpc_config.get_raw_transaction(&req.txid).await? + } else { + default_doge_rpc.get_raw_transaction(&req.txid).await? + }; + + log!(INFO, "check transaction: {:?}", transaction); + + //check whether need to pay fees for transfer. If fee is None, that means paying fees is not need + let (fee, addr) = read_state(|s| s.get_transfer_fee_info(&req.target_chain_id)); + match fee { + None => {} + Some(fee_value) => { + let mut found_fee_utxo = false; + let fee_collector = addr.unwrap(); + for out in &transaction.output { + let (_, addr_opt) = + classify_script(out.script_pubkey.as_bytes(), &DOGE_MAIN_NET_CHAIN); + if let Some(addr) = addr_opt { + let addr_str = addr.to_string(); + if addr_str.eq(&fee_collector) && out.value as u128 == fee_value { + found_fee_utxo = true; + break; + } + } + } + if !found_fee_utxo { + return Err(CustomsError::NotPayFees); + } + } + } + + // receiver should be destination address + let destination = Destination::new(req.target_chain_id.clone(), req.receiver.clone(), None); + + let (destination_to_address, _) = read_state(|s| s.get_address(destination.clone()))?; + + let mut amount = 0; + let first_input = transaction + .input + .first() + .ok_or(CustomsError::DepositUtxoNotFound( + req.txid.clone(), + destination.clone(), + ))?; + + let transaction_of_input = default_doge_rpc + .get_raw_transaction(&first_input.prevout.txid.to_string()) + .await?; + let output_of_input = transaction_of_input + .output + .get(first_input.prevout.vout as usize) + .ok_or(CustomsError::CustomError("input not found".to_string()))?; + + let sender = classify_script( + output_of_input.script_pubkey.as_bytes(), + &DOGE_MAIN_NET_CHAIN, + ) + .1 + .map(|e| e.to_string().clone()) + .ok_or(CustomsError::CustomError( + "failed to get sender from output_of_input".to_string(), + ))?; + + for tx_out in transaction.output.clone() { + let (_, addr_opt) = classify_script(tx_out.script_pubkey.as_bytes(), &DOGE_MAIN_NET_CHAIN); + if let Some(addr) = addr_opt { + if addr.to_string() == destination_to_address.to_string() { + amount += tx_out.value; + } + } + } + + if amount == 0 { + return Err(CustomsError::DepositUtxoNotFound( + req.txid.clone(), + destination, + )); + } + + Ok((transaction, amount, sender)) +} + +pub fn finalize_lock_ticket_task() { + ic_cdk::spawn(async { + let _guard = match crate::guard::TimerLogicGuard::new(FINALIZE_LOCK_TICKET_NAME.to_string()) + { + Some(guard) => guard, + None => return, + }; + finalize_lock_ticket_request().await; + }); +} + +pub async fn finalize_lock_ticket_request() { + let now = ic_cdk::api::time(); + let should_check_finalizations = read_state(|s| { + let wait_time = finalization_time_estimate(s.min_confirmations); + s.pending_lock_ticket_requests + .iter() + .filter(|&req| { + let wait_time = wait_time.as_nanos() as u64; + (req.1.received_at + wait_time < now) && (req.1.received_at + wait_time * 6 > now) + }) + .map(|req| (req.0.clone(), req.1.clone())) + .collect::>() + }); + for (txid, _) in should_check_finalizations.clone() { + match check_tx_confirmation(txid.clone()).await { + Ok(can_finalize) => { + if can_finalize { + match finalize_ticket(txid.clone().into()).await { + Ok(_) => { + log!(INFO, "finalize lock success: {:?}", txid); + } + Err(e) => { + log!(ERROR, "finalize lock error: {:?}", e); + } + } + } + } + Err(e) => { + log!(ERROR, "finalize lock error: {:?}", e); + } + } + } +} + +pub async fn check_tx_confirmation(txid: Txid) -> Result { + let doge_rpc: DogeRpc = read_state(|s| s.default_doge_rpc_config.clone()).into(); + let tx_out = doge_rpc.get_tx_out(txid.to_string().as_str()).await?; + let min_confirmations = read_state(|s| s.min_confirmations); + return Ok(tx_out.confirmations >= min_confirmations); +} + +async fn finalize_ticket(txid: Txid) -> Result<(), CustomsError> { + let hub_principal = read_state(|s| s.hub_principal); + hub::finalize_ticket(hub_principal, txid.to_string()) + .await + .map_err(|e| CustomsError::CallError(hub_principal, e.method, e.reason.to_string()))?; + + mutate_state(|s| { + let v = s + .pending_lock_ticket_requests + .remove(&txid) + .ok_or(CustomsError::CustomError( + "pending lock ticket request not found".to_string(), + ))?; + s.finalized_lock_ticket_requests_map + .insert(txid.clone(), v.clone()); + s.save_utxo(v)?; + + Ok(()) + })?; + + Ok(()) +} diff --git a/customs/doge/src/errors.rs b/customs/doge/src/errors.rs new file mode 100644 index 00000000..09dba146 --- /dev/null +++ b/customs/doge/src/errors.rs @@ -0,0 +1,57 @@ +use thiserror::Error; +use candid::{CandidType, Deserialize, Nat, Principal}; + +use crate::types::Destination; + +#[derive(CandidType, Clone, Default, Debug, Deserialize, PartialEq, Eq, Error)] +pub enum CustomsError { + #[error("Call {0} of {1} failed, reason: {2:?}")] + CallError(Principal, String, String), + #[error("temp unavailable: {0}")] + TemporarilyUnavailable(String), + #[error("AlreadySubmitted")] + AlreadySubmitted, + #[error("AlreadyProcessed")] + AlreadyProcessed, + #[error("DepositUtxoNotFound, txid: {0}, destination: {1:?}")] + DepositUtxoNotFound(String, Destination), + #[error("TxNotFoundInMemPool")] + TxNotFoundInMemPool, + #[error("InvalidRuneId: {0}")] + InvalidRuneId(String), + #[error("InvalidTxId")] + InvalidTxId, + #[error("InvalidTxReceiver")] + InvalidTxReceiver, + #[error("UnsupportedChainId: {0}")] + UnsupportedChainId(String), + #[error("UnsupportedToken: {0}")] + UnsupportedToken(String), + #[error("SendTicketErr: {0}")] + SendTicketErr(String), + #[error("RpcError: {0}")] + RpcError(String), + #[error("AmountIsZero")] + AmountIsZero, + #[error("OrdTxError: {0}")] + OrdTxError(String), + #[error("NotBridgeTx")] + NotBridgeTx, + #[error("InvalidArgs: {0}")] + InvalidArgs(String), + #[error("NotPayFees")] + NotPayFees, + #[default] + #[error("Unknown")] + Unknown, + #[error("CustomError: {0}")] + CustomError(String), + #[error("ECDSAPublicKeyNotFound")] + ECDSAPublicKeyNotFound, + #[error("Http out call failed, code: {0:?}, message: {1}, request: {2}")] + HttpOutCallError(String, String, String), + #[error("Http status code: {0:?}, url: {1}, body: {2}")] + HttpStatusError(Nat, String, String), + #[error("Http out call exceed retry limit")] + HttpOutExceedRetryLimit, +} \ No newline at end of file diff --git a/customs/doge/src/generate_ticket.rs b/customs/doge/src/generate_ticket.rs new file mode 100644 index 00000000..74d2f932 --- /dev/null +++ b/customs/doge/src/generate_ticket.rs @@ -0,0 +1,130 @@ +use std::str::FromStr; + +use candid::{CandidType, Deserialize}; +use ic_canister_log::log; +use serde::Serialize; + +use omnity_types::ic_log::INFO; +use omnity_types::{ChainState, Ticket, TicketType, TxAction}; + +use crate::doge::tatum_rpc; +use crate::dogeoin_to_custom::check_transaction; +use crate::doge::transaction::Txid; +use crate::errors::CustomsError; +use crate::hub; +use crate::state::{mutate_state, read_state}; +use crate::types::{serialize_hex, Destination, GenTicketStatus, LockTicketRequest}; + +#[derive(Clone, CandidType, Serialize, Deserialize, Debug)] +pub struct GenerateTicketWithTxidArgs { + pub txid: String, + pub target_chain_id: String, + pub token_id: String, + pub receiver: String, +} + +#[derive(Clone, CandidType, Serialize, Deserialize, Debug)] +pub struct GenerateTicketArgs { + pub target_chain_id: String, + pub token_id: String, + pub receiver: String, +} + +pub async fn get_ungenerated_txids(args: GenerateTicketArgs) -> Result, CustomsError> { + let dest = Destination::new( + args.target_chain_id, + args.receiver, + None + ); + let deposit_address = read_state(|s| s.get_address(dest)).map(|a| a.0.to_string())?; + + let tatum_rpc_config = read_state(|s| s.tatum_api_config.clone()); + let tatum_rpc = tatum_rpc::TatumDogeRpc::new( + tatum_rpc_config.url, + tatum_rpc_config.api_key + ); + let txids = tatum_rpc.get_transactions_by_address(deposit_address).await.map_err(|e| CustomsError::RpcError(format!("{}", e)))?; + + let filtered_txids: Vec = txids.into_iter().filter(|txid| { + read_state(|s| { + + let type_txid: crate::types::Txid = txid.to_owned().into(); + s.generate_ticket_status(&type_txid) == GenTicketStatus::Unknown + }) + }).collect(); + + Ok(filtered_txids) +} + +pub async fn generate_ticket(args: GenerateTicketWithTxidArgs) -> Result<(), CustomsError> { + log!(INFO, "received generate_ticket: {:?}", args.clone()); + if read_state(|s| s.chain_state == ChainState::Deactive) { + return Err(CustomsError::TemporarilyUnavailable( + "chain state is deactive!".into(), + )); + } + + let txid = Txid::from_str(&args.txid).map_err(|_| CustomsError::InvalidTxId)?; + if !read_state(|s| { + s.counterparties + .get(&args.target_chain_id) + .is_some_and(|c| c.chain_state == ChainState::Active) + }) { + return Err(CustomsError::UnsupportedChainId( + args.target_chain_id.clone(), + )); + } + + read_state(|s| { + s.tokens + .get(&args.token_id) + .cloned() + .ok_or(CustomsError::UnsupportedToken(args.token_id.clone())) + })?; + + read_state(|s| match s.generate_ticket_status(&(txid.clone().into())) { + GenTicketStatus::Pending(_) | GenTicketStatus::Confirmed(_) => { + Err(CustomsError::AlreadySubmitted) + } + GenTicketStatus::Finalized(_) => Err(CustomsError::AlreadyProcessed), + GenTicketStatus::Unknown => Ok(()), + })?; + let (chain_id, hub_principal, min_deposit_amount) = read_state(|s| (s.chain_id.clone(), s.hub_principal, s.min_deposit_amount)); + let (transaction, amount, sender) = check_transaction(args.clone()).await?; + + if amount < min_deposit_amount { + return Err(CustomsError::CustomError(format!("The amount of the transaction is less than the minimum deposit amount: {}", min_deposit_amount))); + } + + hub::pending_ticket( + hub_principal, + Ticket { + ticket_id: args.txid.clone(), + ticket_type: TicketType::Normal, + ticket_time: ic_cdk::api::time(), + src_chain: chain_id, + dst_chain: args.target_chain_id.clone(), + action: TxAction::Transfer, + token: args.token_id.clone(), + amount: amount.to_string(), + sender: Some(sender), + receiver: args.receiver.clone(), + memo: None, + }, + ) + .await + .map_err(|err| CustomsError::SendTicketErr(format!("{}", err)))?; + let request = LockTicketRequest { + target_chain_id: args.target_chain_id, + receiver: args.receiver, + token_id: args.token_id, + amount: amount.to_string(), + txid: txid.into(), + received_at: ic_cdk::api::time(), + transaction_hex: serialize_hex(&transaction) + }; + mutate_state(|s| { + s.pending_lock_ticket_requests.insert(request.txid.clone().into(), request); + }); + Ok(()) +} diff --git a/customs/doge/src/guard.rs b/customs/doge/src/guard.rs new file mode 100644 index 00000000..16a2ec10 --- /dev/null +++ b/customs/doge/src/guard.rs @@ -0,0 +1,27 @@ +use crate::state::mutate_state; + +#[must_use] +pub struct TimerLogicGuard(String); + +impl TimerLogicGuard { + pub fn new(task_name: String) -> Option { + mutate_state(|s| { + let running = s + .is_timer_running + .get(&task_name) + .cloned() + .unwrap_or_default(); + if running { + return None; + } + s.is_timer_running.insert(task_name.clone(), true); + Some(TimerLogicGuard(task_name)) + }) + } +} + +impl Drop for TimerLogicGuard { + fn drop(&mut self) { + mutate_state(|s| s.is_timer_running.remove(&self.0)); + } +} diff --git a/customs/doge/src/hub.rs b/customs/doge/src/hub.rs new file mode 100644 index 00000000..c5b632d8 --- /dev/null +++ b/customs/doge/src/hub.rs @@ -0,0 +1,93 @@ +use crate::call_error::{CallError, Reason}; +use candid::utils::ArgumentEncoder; +use candid::CandidType; +use candid::Principal; +use ic_canister_log::log; +use omnity_types::ic_log::INFO; +use omnity_types::Directive; +use omnity_types::TicketId; +use omnity_types::Topic; +use omnity_types::{self, ChainId, Seq, Ticket}; + +pub async fn query_tickets( + hub_principal: Principal, + offset: u64, + limit: u64, +) -> Result, CallError> { + call( + hub_principal, + "query_tickets".into(), + (None::>, offset, limit), + ) + .await +} + +pub async fn update_tx_hash( + hub_principal: Principal, + ticket_id: TicketId, + mint_tx_hash: String, +) -> Result<(), CallError> { + let r = call( + hub_principal, + "update_tx_hash".into(), + (ticket_id.clone(), mint_tx_hash.clone()), + ) + .await; + if r.is_ok() { + log!( + INFO, + "[rewrite tx_hash] write doge unlock ticket({}) tx hash({}) success.", + ticket_id, + mint_tx_hash + ); + } + r +} + +pub async fn query_directives( + hub_principal: Principal, + offset: u64, + limit: u64, +) -> Result, CallError> { + call( + hub_principal, + "query_directives".into(), + ( + None::>, + None::>, + offset, + limit, + ), + ) + .await +} + +pub async fn pending_ticket(hub_principal: Principal, ticket: Ticket) -> Result<(), CallError> { + call(hub_principal, "pending_ticket".into(), (ticket,)).await +} + +pub async fn finalize_ticket(hub_principal: Principal, ticket_id: String) -> Result<(), CallError> { + call(hub_principal, "finalize_ticket".into(), (ticket_id,)).await +} + +async fn call( + hub_principal: Principal, + method: String, + args: T, +) -> Result +where + R: for<'a> candid::Deserialize<'a> + CandidType, +{ + let resp: (Result,) = + ic_cdk::api::call::call(hub_principal, &method, args) + .await + .map_err(|(code, message)| CallError { + method: method.to_string(), + reason: Reason::from_reject(code, message), + })?; + let data = resp.0.map_err(|err| CallError { + method: method.to_string(), + reason: Reason::CanisterError(err.to_string()), + })?; + Ok(data) +} diff --git a/customs/doge/src/hub_to_custom.rs b/customs/doge/src/hub_to_custom.rs new file mode 100644 index 00000000..def89001 --- /dev/null +++ b/customs/doge/src/hub_to_custom.rs @@ -0,0 +1,129 @@ +use crate::constants::{BATCH_QUERY_LIMIT, FETCH_HUB_DIRECTIVE_NAME, FETCH_HUB_TICKET_NAME}; +use crate::state::{mutate_state, read_state}; +use crate::{audit, hub}; +use ic_canister_log::log; +use omnity_types::ic_log::ERROR; +use omnity_types::{ChainState, Directive, Factor, Seq, Ticket}; + +async fn process_tickets() { + if read_state(|s| s.chain_state == ChainState::Deactive) { + return; + } + let (hub_principal, offset) = read_state(|s| (s.hub_principal, s.next_ticket_seq)); + match hub::query_tickets(hub_principal, offset, BATCH_QUERY_LIMIT).await { + Ok(tickets) => { + store_tickets(tickets, offset); + } + Err(err) => { + log!( + ERROR, + "[process tickets] failed to query tickets, err: {}", + err + ); + } + } +} + +pub fn store_tickets(tickets: Vec<(Seq, Ticket)>, offset: u64) { + let mut next_seq = offset; + for (seq, ticket) in tickets { + if ticket.amount.parse::().is_err() { + log!( + ERROR, + "[process tickets] failed to parse ticket amount: {}", + ticket.amount + ); + next_seq = seq + 1; + continue; + }; + mutate_state(|s| { + let ticketid = ticket.ticket_id.clone(); + s.ticket_id_seq_indexer.insert(ticketid, seq); + s.tickets_queue.insert(seq, ticket); + }); + next_seq = seq + 1; + } + mutate_state(|s| s.next_ticket_seq = next_seq) +} + +async fn process_directives() { + let (hub_principal, offset) = read_state(|s| (s.hub_principal, s.next_directive_seq)); + match hub::query_directives(hub_principal, offset, BATCH_QUERY_LIMIT).await { + Ok(directives) => { + for (seq, directive) in &directives { + let final_directive = directive.clone(); + match directive.clone() { + Directive::AddChain(chain) | Directive::UpdateChain(chain) => { + mutate_state(|s| audit::add_chain(s, chain.clone())); + } + Directive::ToggleChainState(t) => { + mutate_state(|s| { + if let Some(chain) = s.counterparties.get_mut(&t.chain_id) { + chain.chain_state = t.action.clone().into(); + } + if t.chain_id == s.chain_id { + s.chain_state = t.action.into(); + } + }); + } + Directive::UpdateToken(token) => { + mutate_state(|s| audit::add_token(s, token.clone())); + } + Directive::AddToken(token) => { + mutate_state(|s| audit::add_token(s, token)); + } + Directive::UpdateFee(fee) => { + match fee { + Factor::UpdateTargetChainFactor(factor) => { + mutate_state(|s|{ + s.target_chain_factor + .insert(factor.target_chain_id.clone(), factor.target_chain_factor); + }); + } + Factor::UpdateFeeTokenFactor(token_factor) => { + mutate_state(|s|{ + if token_factor.fee_token == s.fee_token { + s.fee_token_factor = Some(token_factor.fee_token_factor); + } + }); + } + } + } + } + mutate_state(|s| s.directives_queue.insert(*seq, final_directive)); + } + let next_seq = directives.last().map_or(offset, |(seq, _)| seq + 1); + mutate_state(|s| { + s.next_directive_seq = next_seq; + }); + } + Err(err) => { + log!( + ERROR, + "[process directives] failed to query directives, err: {:?}", + err + ); + } + }; +} + +pub fn fetch_hub_ticket_task() { + ic_cdk::spawn(async { + let _guard = match crate::guard::TimerLogicGuard::new(FETCH_HUB_TICKET_NAME.to_string()) { + Some(guard) => guard, + None => return, + }; + process_tickets().await; + }); +} + +pub fn fetch_hub_directive_task() { + ic_cdk::spawn(async { + let _guard = match crate::guard::TimerLogicGuard::new(FETCH_HUB_DIRECTIVE_NAME.to_string()) + { + Some(guard) => guard, + None => return, + }; + process_directives().await; + }); +} diff --git a/customs/doge/src/lib.rs b/customs/doge/src/lib.rs new file mode 100644 index 00000000..af9cd5ee --- /dev/null +++ b/customs/doge/src/lib.rs @@ -0,0 +1,80 @@ +mod audit; +mod doge; +mod dogeoin_to_custom; +mod call_error; +mod errors; +mod custom_to_dogecoin; +mod generate_ticket; +mod guard; +mod hub; +mod hub_to_custom; +mod management; +pub mod service; +mod stable_memory; +pub(crate) mod state; +mod tasks; +mod types; + +pub mod constants { + + pub const FETCH_HUB_TICKET_INTERVAL: u64 = 60; + pub const FETCH_HUB_DIRECTIVE_INTERVAL: u64 = 120; + pub const FETCH_HUB_TICKET_NAME: &str = "FETCH_HUB_TICKET"; + pub const FETCH_HUB_DIRECTIVE_NAME: &str = "FETCH_HUB_DIRECTIVE"; + pub const FINALIZE_LOCK_TICKET_NAME: &str = "FINALIZE_GENERATE_TICKET_NAME"; + pub const FINALIZE_LOCK_TICKET_INTERVAL: u64 = 120; + pub const FINALIZE_UNLOCK_TICKET_NAME: &str = "FINALIZE_UNLOCK_TICKET_NAME"; + pub const FINALIZE_UNLOCK_TICKET_INTERVAL: u64 = 120; + pub const SUBMIT_UNLOCK_TICKETS_NAME: &str = "SUBMIT_UNLOCK_TICKETS_NAME"; + pub const SUBMIT_UNLOCK_TICKETS_INTERVAL: u64 = 5; + pub const BATCH_QUERY_LIMIT: u64 = 20; + pub const RPC_RETRY_TIMES: u8 = 3; + pub const SEC_NANOS: u64 = 1_000_000_000; + pub const MIN_NANOS: u64 = 60 * SEC_NANOS; + + pub const KB: u64 = 1024; + pub const KB100: u64 = 100 * KB; +} + +pub mod retry { + use crate::constants::RPC_RETRY_TIMES; + use ic_canister_log::log; + use omnity_types::ic_log::{CRITICAL, ERROR, INFO}; + use std::fmt::Debug; + use std::future::Future; + + pub async fn call_rpc_with_retry< + P: Clone, + T, + E: Default + Clone + ToString + Debug, + R: Future>, + >( + params: P, + call_rpc: fn(params: P) -> R, + ) -> Result { + let mut rs = Err(E::default()); + for i in 0..RPC_RETRY_TIMES { + log!(INFO, "request rpc request times: {}", i + 1); + let call_res = call_rpc(params.clone()).await; + if call_res.is_ok() { + rs = call_res; + break; + } else { + let err = call_res.err().unwrap(); + log!( + ERROR, + "call rpc error: {}", + err.clone().to_string() + ); + rs = Err(err); + } + } + match rs { + Ok(t) => Ok(t), + Err(e) => { + log!(CRITICAL, "rpc error after retry {:?}", &e); + Err(e) + } + } + } +} diff --git a/customs/doge/src/management.rs b/customs/doge/src/management.rs new file mode 100644 index 00000000..6bd2c55e --- /dev/null +++ b/customs/doge/src/management.rs @@ -0,0 +1,63 @@ +use candid::{CandidType, Principal}; +use ic_ic00_types::{ + DerivationPath, ECDSAPublicKeyArgs, ECDSAPublicKeyResponse, EcdsaCurve, EcdsaKeyId, +}; +use serde::de::DeserializeOwned; + +use crate::call_error::{CallError, Reason}; +use crate::types::ECDSAPublicKey; + +async fn call(method: &str, payment: u64, input: &I) -> Result +where + I: CandidType, + O: CandidType + DeserializeOwned, +{ + let balance = ic_cdk::api::canister_balance128(); + if balance < payment as u128 { + return Err(CallError { + method: method.to_string(), + reason: Reason::OutOfCycles, + }); + } + + let res: Result<(O,), _> = ic_cdk::api::call::call_with_payment( + Principal::management_canister(), + method, + (input,), + payment, + ) + .await; + + match res { + Ok((output,)) => Ok(output), + Err((code, msg)) => Err(CallError { + method: method.to_string(), + reason: Reason::from_reject(code, msg), + }), + } +} + +pub async fn ecdsa_public_key( + key_name: String, + derivation_path: DerivationPath, +) -> Result { + // Retrieve the public key of this canister at the given derivation path + // from the ECDSA API. + call( + "ecdsa_public_key", + /*payment=*/ 0, + &ECDSAPublicKeyArgs { + canister_id: None, + derivation_path, + key_id: EcdsaKeyId { + curve: EcdsaCurve::Secp256k1, + name: key_name, + }, + }, + ) + .await + .map(|response: ECDSAPublicKeyResponse| ECDSAPublicKey { + public_key: response.public_key, + chain_code: response.chain_code, + }) +} \ No newline at end of file diff --git a/customs/doge/src/service.rs b/customs/doge/src/service.rs new file mode 100644 index 00000000..87e5f821 --- /dev/null +++ b/customs/doge/src/service.rs @@ -0,0 +1,250 @@ +use crate::custom_to_dogecoin::SendTicketResult; +use crate::doge::transaction::Txid; +use crate::dogeoin_to_custom::query_and_save_utxo_for_payment_address; +use crate::errors::CustomsError; +use crate::generate_ticket::{GenerateTicketArgs, GenerateTicketWithTxidArgs}; +use crate::state::{mutate_state, read_state, replace_state, DogeState, StateProfile}; +use crate::tasks::start_tasks; +use crate::types::{ + Destination, LockTicketRequest, MultiRpcConfig, ReleaseTokenStatus, RpcConfig, TokenResp +}; +use candid::{CandidType, Deserialize, Principal}; +use ic_canister_log::log; +use ic_canisters_http_types::{HttpRequest, HttpResponse}; +use ic_cdk::api::management_canister::http_request; +use ic_cdk::api::management_canister::http_request::TransformArgs; +use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; +use omnity_types::ic_log::{ERROR, INFO}; +use omnity_types::{ChainId, Seq}; +use std::str::FromStr; + +#[init] +fn init(args: InitArgs) { + replace_state(DogeState::init(args).expect("params error")); + start_tasks(); +} + +#[pre_upgrade] +fn pre_upgrade() { + read_state(|s| s.pre_upgrade()); +} + +#[post_upgrade] +fn post_upgrade() { + DogeState::post_upgrade(); + start_tasks(); +} + +#[query] +pub fn get_finalized_lock_ticket_txids() -> Vec { + read_state(|s| { + s.finalized_lock_ticket_requests_map + .iter() + .map(|e| e.1.txid.to_string()) + .collect() + }) +} + +#[query] +pub fn get_finalized_unlock_ticket_results() -> Vec { + read_state(|s| { + s.finalized_unlock_ticket_results_map + .iter() + .map(|e| e.1.clone()) + .collect() + }) +} + +#[query(hidden = true)] +fn http_request(req: HttpRequest) -> HttpResponse { + if ic_cdk::api::data_certificate().is_none() { + ic_cdk::trap("update call rejected"); + } + omnity_types::ic_log::http_request(req) +} + +#[update] +pub async fn generate_ticket_by_txid(req: GenerateTicketWithTxidArgs)-> Result<(), CustomsError> { + match crate::generate_ticket::generate_ticket(req.clone()).await { + Ok(_) => { + log!(INFO, "success to generate_ticket_by_txid, req: {:?}", req); + Ok(()) + }, + Err(e) => { + log!(ERROR, "failed to generate_ticket_by_txid error: {:?}", e); + Err(CustomsError::from(e)) + } + } +} + +#[update] +pub async fn generate_ticket(req: GenerateTicketArgs) -> Result, CustomsError> { + let txids = crate::generate_ticket::get_ungenerated_txids(req.clone()).await?; + log!(INFO, "find txids for generate_ticket: {:?}", txids); + let mut success_txids = vec![]; + for txid in txids { + let args = GenerateTicketWithTxidArgs { + txid: txid.to_string(), + target_chain_id: req.target_chain_id.clone(), + token_id: req.token_id.clone(), + receiver: req.receiver.clone(), + }; + match crate::generate_ticket::generate_ticket(args).await { + Ok(_) => { + log!(INFO, "success to generate_ticket, txid: {:?}", txid); + success_txids.push(txid.to_string()); + }, + Err(e) => { + log!(ERROR, "generate_ticket error: {:?}", e); + }, + } + + } + + Ok(success_txids) +} + +#[query] +fn get_platform_fee(target_chain: ChainId) -> (Option, Option) { + read_state(|s| s.get_transfer_fee_info(&target_chain)) +} + +#[query] +pub fn get_deposit_address( + target_chain_id: String, + receiver: String, +) -> Result { + let dest = Destination::new(target_chain_id, receiver, None); + read_state(|s| s.get_address(dest)).map(|a| a.0.to_string()) +} + +#[query(guard = "is_admin")] +pub fn query_state() -> StateProfile { + read_state(|s| StateProfile::from(s)) +} + +#[update(guard = "is_admin")] +pub fn set_fee_collector(addr: String) { + mutate_state(|s| s.fee_collector = addr); +} + +#[query] +pub fn get_fee_payment_address() -> Result { + mutate_state(|s| s.get_address(Destination::fee_payment_address())).map(|a| a.0.to_string()) +} + +#[update(guard = "is_admin")] +pub async fn save_utxo_for_payment_address(txid: String) -> Result { + query_and_save_utxo_for_payment_address(txid).await +} + +#[update(guard = "is_admin")] +pub fn set_min_deposit_amount(amount: u64) { + mutate_state(|s| s.min_deposit_amount = amount); +} + +#[query] +fn release_token_status(ticket_id: String) -> ReleaseTokenStatus { + read_state(|s| s.unlock_tx_status(&ticket_id)) +} + +#[query(guard = "is_admin")] +pub fn pending_unlock_tickets(seq: Seq) -> String { + let r = read_state(|s| s.flight_unlock_ticket_map.get(&seq).cloned().unwrap()); + serde_json::to_string(&r).unwrap() +} + +#[update(guard = "is_admin")] +pub async fn init_ecdsa_public_key() -> Result<(), CustomsError> { + crate::state::init_ecdsa_public_key().await.map(|_| ()) +} + +#[update(guard = "is_admin")] +pub async fn set_tatum_api_config(url: String, api_key: Option) { + mutate_state(|s| { + s.tatum_api_config = RpcConfig { url, api_key }; + }); +} + +#[update(guard = "is_admin")] +pub async fn set_default_doge_rpc_config(url: String, api_key: Option) { + mutate_state(|s| { + s.default_doge_rpc_config = RpcConfig { url, api_key }; + }); +} + +#[update(guard = "is_admin")] +pub async fn set_multi_rpc_config( + multi_rpc_config: MultiRpcConfig +) { + mutate_state(|s| { + s.multi_rpc_config = multi_rpc_config; + }); +} + +#[query(hidden = true)] +fn transform(raw: TransformArgs) -> http_request::HttpResponse { + http_request::HttpResponse { + status: raw.response.status.clone(), + body: raw.response.body.clone(), + headers: vec![], + } +} + +#[update(guard = "is_admin")] +pub async fn resend_unlock_ticket(seq: Seq, fee_rate: Option) -> Result { + match crate::custom_to_dogecoin::submit_unlock_ticket(seq, fee_rate).await { + Ok(r) => { + log!( + INFO, + "success to resend_unlock_ticket, seq: {:?}, txid: {:?}", + seq, + r.txid.to_string() + ); + mutate_state(|s| s.flight_unlock_ticket_map.insert(seq, r.clone())); + Ok(serde_json::to_string(&r).unwrap()) + } + Err(e) => { + log!(ERROR, "resend_unlock_ticket error: {:?}", e); + return Err("resend_unlock_ticket error".to_string()); + } + } +} + +// #[update] +// pub async fn test_rpc(rpc_config: RpcConfig, txid: String) -> Result { +// let doge_rpc = crate::doge::rpc::DogeRpc::from(rpc_config); +// doge_rpc.get_raw_transaction(txid.as_str()).await.map(|r| format!("{:?}", r)) +// } + +#[query] +fn get_token_list() -> Vec { + read_state(|s| s.tokens.values().map(|t| t.clone().into()).collect()) +} + +#[query(guard = "is_admin")] +fn query_finalized_lock_tickets(txid: String) -> Option { + let txid = Txid::from_str(txid.as_str()).unwrap(); + read_state(|s| s.finalized_lock_ticket_requests_map.get(&txid.into())) +} + +#[derive(CandidType, Deserialize)] +pub struct InitArgs { + pub admins: Vec, + pub hub_principal: Principal, + // pub network: Network, + pub chain_id: String, + // pub indexer_principal: Principal, + pub fee_token: String, + pub default_doge_rpc_config: RpcConfig, +} + +fn is_admin() -> Result<(), String> { + let c = ic_cdk::caller(); + match ic_cdk::api::is_controller(&c) || read_state(|s| s.admins.contains(&c)) { + true => Ok(()), + false => Err("permission deny".to_string()), + } +} + +ic_cdk::export_candid!(); diff --git a/customs/doge/src/stable_memory.rs b/customs/doge/src/stable_memory.rs new file mode 100644 index 00000000..b9a85f0a --- /dev/null +++ b/customs/doge/src/stable_memory.rs @@ -0,0 +1,82 @@ +use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory}; +use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap}; +use omnity_types::{Directive, Seq, Ticket}; +use std::cell::RefCell; +use std::collections::BTreeMap; + +use crate::custom_to_dogecoin::SendTicketResult; +use crate::types::{Destination, LockTicketRequest, Txid, Utxo}; + +pub type InnerMemory = DefaultMemoryImpl; +pub type Memory = VirtualMemory; +pub const UPGRADE_STASH_MEMORY_ID: MemoryId = MemoryId::new(0); +pub const UNLOCK_TICKETS_MEMORY_ID: MemoryId = MemoryId::new(1); +pub const DIRECTIVES_MEMORY_ID: MemoryId = MemoryId::new(2); +pub const DEPOSIT_TX_MEMORY_ID: MemoryId = MemoryId::new(3); +pub const UNLOCK_TICKETS_RESULTS_MEMORY_ID: MemoryId = MemoryId::new(4); +pub const LOCK_TICKETS_REQUESTS_MEMORY_ID: MemoryId = MemoryId::new(5); + +thread_local! { + static MEMORY: RefCell> = RefCell::new(Some(InnerMemory::default())); + + static MEMORY_MANAGER: RefCell>> = + RefCell::new(Some(MemoryManager::init(MEMORY.with(|m| m.borrow().clone().unwrap())))); + + static COLLECTED_UTXOS: RefCell> = + const { RefCell::new(BTreeMap::new()) }; + +} + +fn with_memory_manager(f: impl FnOnce(&MemoryManager) -> R) -> R { + MEMORY_MANAGER.with(|cell| { + f(cell + .borrow() + .as_ref() + .expect("memory manager not initialized")) + }) +} + +pub fn get_unlock_tickets_memory() -> Memory { + with_memory_manager(|m| m.get(UNLOCK_TICKETS_MEMORY_ID)) +} + +pub fn get_directives_memory() -> Memory { + with_memory_manager(|m| m.get(DIRECTIVES_MEMORY_ID)) +} + +pub fn get_deposit_tx_memory() -> Memory { + with_memory_manager(|m| m.get(DEPOSIT_TX_MEMORY_ID)) +} + +pub fn get_upgrade_stash_memory() -> Memory { + with_memory_manager(|m| m.get(UPGRADE_STASH_MEMORY_ID)) +} + +pub fn get_unlock_ticket_results_memory() -> Memory { + with_memory_manager(|m| m.get(UNLOCK_TICKETS_RESULTS_MEMORY_ID)) +} + +pub fn get_lock_ticket_requests_memory() -> Memory { + with_memory_manager(|m| m.get(LOCK_TICKETS_REQUESTS_MEMORY_ID)) +} + +pub fn init_unlock_tickets_queue() -> StableBTreeMap { + StableBTreeMap::init(get_unlock_tickets_memory()) +} + +pub fn init_directives_queue() -> StableBTreeMap { + StableBTreeMap::init(get_directives_memory()) +} + +// pub fn init_deposit_fee_tx_set() +pub fn init_deposit_fee_tx_set() -> StableBTreeMap { + StableBTreeMap::init(get_deposit_tx_memory()) +} + +pub fn init_unlock_ticket_results() -> StableBTreeMap { + StableBTreeMap::init(get_unlock_ticket_results_memory()) +} + +pub fn init_lock_ticket_requests() -> StableBTreeMap { + StableBTreeMap::init(get_lock_ticket_requests_memory()) +} \ No newline at end of file diff --git a/customs/doge/src/state.rs b/customs/doge/src/state.rs new file mode 100644 index 00000000..b02538e9 --- /dev/null +++ b/customs/doge/src/state.rs @@ -0,0 +1,355 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::time::Duration; + +use candid::{CandidType, Principal}; +use ic_canister_log::log; +use ic_ic00_types::DerivationPath; +use ic_stable_structures::writer::Writer; +use ic_stable_structures::StableBTreeMap; +use serde::{Deserialize, Serialize}; + +use omnity_types::ic_log::INFO; +use omnity_types::{Chain, ChainId, ChainState, Directive, Network, Seq, Ticket, TicketId, Token, TokenId}; + +use crate::constants::MIN_NANOS; +use crate::custom_to_dogecoin::SendTicketResult; +use crate::doge::chainparams::{chain_from_key_bits, ChainParams, KeyBits, MAIN_NET_DOGE}; +use crate::doge::ecdsa::derive_public_key; +use crate::doge::script::Address; +use crate::doge::transaction::Transaction; +use crate::doge::script; +use crate::errors::CustomsError; +use crate::service::InitArgs; +use crate::stable_memory; +use crate::stable_memory::Memory; +use crate::types::{deserialize_hex, wrap_to_customs_error, Destination, ECDSAPublicKey, GenTicketStatus, LockTicketRequest, MultiRpcConfig, ReleaseTokenStatus, RpcConfig, Txid, Utxo}; + +thread_local! { + static STATE: RefCell> = const {RefCell::new(None)}; +} + +#[derive(Deserialize, Serialize)] +pub struct DogeState { + pub admins: Vec, + pub doge_chain: KeyBits, + pub doge_fee_rate: Option, + pub min_confirmations: u32, + #[serde(default)] + pub min_deposit_amount: u64, + pub ecdsa_key_name: String, + pub ecdsa_public_key: Option, + pub hub_principal: Principal, + pub chain_id: String, + pub tokens: BTreeMap, + pub counterparties: BTreeMap, + pub chain_state: ChainState, + pub next_ticket_seq: u64, + pub next_directive_seq: u64, + pub next_consume_ticket_seq: u64, + + #[serde(skip, default = "crate::stable_memory::init_unlock_tickets_queue")] + pub tickets_queue: StableBTreeMap, + pub flight_unlock_ticket_map: BTreeMap, + pub ticket_id_seq_indexer: BTreeMap, + + #[serde(skip, default = "crate::stable_memory::init_unlock_ticket_results")] + pub finalized_unlock_ticket_results_map: StableBTreeMap, + + //lock tickets storage + pub pending_lock_ticket_requests: BTreeMap, + #[serde(skip, default = "crate::stable_memory::init_lock_ticket_requests")] + pub finalized_lock_ticket_requests_map: StableBTreeMap, + + #[serde(skip, default = "crate::stable_memory::init_directives_queue")] + pub directives_queue: StableBTreeMap, + #[serde(skip)] + pub is_timer_running: BTreeMap, + + pub deposited_utxo: Vec<(Utxo, Destination)>, + // omnity fee + #[serde(default)] + pub fee_token: String, + pub fee_collector: String, + pub fee_token_factor: Option, + pub target_chain_factor: BTreeMap, + + // rpc + // https://dashboard.tatum.io, use custom rpc method + #[serde(default)] + pub tatum_api_config: RpcConfig, + + #[serde(default)] + pub default_doge_rpc_config: RpcConfig, + + #[serde(default)] + pub multi_rpc_config: MultiRpcConfig, + #[serde(skip, default = "crate::stable_memory::init_deposit_fee_tx_set")] + pub deposit_fee_tx_set: StableBTreeMap, + #[serde(default)] + pub fee_payment_utxo: Vec, +} + +#[derive(Serialize, Deserialize, CandidType, Clone)] +pub struct StateProfile { + pub admins: Vec, + pub doge_chain: KeyBits, + pub doge_fee_rate: Option, + pub min_confirmations: u32, + pub min_deposit_amount: u64, + pub ecdsa_key_name: String, + pub ecdsa_public_key: Option, + pub hub_principal: Principal, + pub chain_id: String, + pub tokens: BTreeMap, + pub counterparties: BTreeMap, + pub chain_state: ChainState, + pub next_ticket_seq: u64, + pub next_directive_seq: u64, + pub next_consume_ticket_seq: u64, + pub flight_unlock_ticket_map: BTreeMap, + pub pending_lock_ticket_requests: BTreeMap, + + pub deposited_utxo: Vec<(Utxo, Destination)>, + + pub fee_token: String, + pub fee_collector: String, + pub fee_token_factor: Option, + pub target_chain_factor: BTreeMap, + + pub tatum_rpc_config: RpcConfig, + pub multi_rpc_config: MultiRpcConfig, + pub fee_payment_utxo: Vec, + +} + +impl From<&DogeState> for StateProfile { + fn from(value: &DogeState) -> Self { + StateProfile { + admins: value.admins.clone(), + doge_chain: value.doge_chain, + doge_fee_rate: value.doge_fee_rate, + min_confirmations: value.min_confirmations, + min_deposit_amount: value.min_deposit_amount, + ecdsa_key_name: value.ecdsa_key_name.clone(), + ecdsa_public_key: value.ecdsa_public_key.clone(), + hub_principal: value.hub_principal, + chain_id: value.chain_id.clone(), + tokens: value.tokens.clone(), + counterparties: value.counterparties.clone(), + chain_state: value.chain_state.clone(), + next_ticket_seq: value.next_ticket_seq, + next_directive_seq: value.next_directive_seq, + next_consume_ticket_seq: value.next_consume_ticket_seq, + pending_lock_ticket_requests: value.pending_lock_ticket_requests.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), + flight_unlock_ticket_map: value.flight_unlock_ticket_map.clone(), + deposited_utxo: value.deposited_utxo.clone(), + fee_token: value.fee_token.clone(), + fee_collector: value.fee_collector.clone(), + fee_token_factor: value.fee_token_factor, + target_chain_factor: value.target_chain_factor.clone(), + tatum_rpc_config: value.tatum_api_config.clone(), + multi_rpc_config: value.multi_rpc_config.clone(), + fee_payment_utxo: value.fee_payment_utxo.clone(), + } + } +} + +impl DogeState { + pub fn init(args: InitArgs) -> anyhow::Result { + let ret = DogeState { + admins: args.admins, + doge_fee_rate: Option::None, + doge_chain: MAIN_NET_DOGE, + hub_principal: args.hub_principal, + chain_id: args.chain_id, + tokens: Default::default(), + counterparties: Default::default(), + chain_state: ChainState::Active, + ecdsa_key_name: Network::Mainnet.key_id().name, + flight_unlock_ticket_map: BTreeMap::default(), + ecdsa_public_key: None, + next_ticket_seq: 0, + next_directive_seq: 0, + next_consume_ticket_seq: 0, + tickets_queue: StableBTreeMap::init(crate::stable_memory::get_unlock_tickets_memory()), + directives_queue: StableBTreeMap::init(crate::stable_memory::get_directives_memory()), + is_timer_running: Default::default(), + pending_lock_ticket_requests: Default::default(), + fee_collector: "".to_string(), + fee_token_factor: None, + min_confirmations: 4, + min_deposit_amount: 0, + ticket_id_seq_indexer: Default::default(), + target_chain_factor: Default::default(), + fee_token: args.fee_token, + deposited_utxo: vec![], + tatum_api_config: RpcConfig::default(), + default_doge_rpc_config: args.default_doge_rpc_config, + multi_rpc_config: MultiRpcConfig::default(), + deposit_fee_tx_set: StableBTreeMap::init(crate::stable_memory::get_deposit_tx_memory()), + fee_payment_utxo: vec![], + finalized_unlock_ticket_results_map: StableBTreeMap::init(crate::stable_memory::get_unlock_ticket_results_memory()), + finalized_lock_ticket_requests_map: StableBTreeMap::init(crate::stable_memory::get_lock_ticket_requests_memory()), + }; + Ok(ret) + } + + pub fn pre_upgrade(&self) { + let mut state_bytes = vec![]; + let _ = ciborium::ser::into_writer(self, &mut state_bytes); + let len = state_bytes.len() as u32; + let mut memory = crate::stable_memory::get_upgrade_stash_memory(); + let mut writer = Writer::new(&mut memory, 0); + writer + .write(&len.to_le_bytes()) + .expect("failed to save hub state len"); + writer + .write(&state_bytes) + .expect("failed to save hub state"); + } + + pub fn post_upgrade() { + use ic_stable_structures::Memory; + let memory = stable_memory::get_upgrade_stash_memory(); + // Read the length of the state bytes. + let mut state_len_bytes = [0; 4]; + memory.read(0, &mut state_len_bytes); + let state_len = u32::from_le_bytes(state_len_bytes) as usize; + let mut state_bytes = vec![0; state_len]; + memory.read(4, &mut state_bytes); + let state: DogeState = + ciborium::de::from_reader(&*state_bytes).expect("failed to decode state"); + replace_state(state); + log!(INFO, "post upgradge sucessed!!"); + } + + pub fn get_transfer_fee_info( + &self, + target_chain_id: &ChainId, + ) -> (Option, Option) { + if target_chain_id.ne("Ethereum") { + return (None, None); + } + let fee = self.fee_token_factor.and_then(|f| { + self.target_chain_factor + .get(target_chain_id) + .map(|factor| f * factor) + }); + (fee, fee.map(|_| self.fee_collector.clone())) + } + + pub fn generate_ticket_status(&self, tx_id: &Txid) -> GenTicketStatus { + if let Some(req) = self.pending_lock_ticket_requests.get(tx_id) { + return GenTicketStatus::Pending(req.clone()); + } + + if let Some(req) = self.finalized_lock_ticket_requests_map.get(tx_id) { + return GenTicketStatus::Finalized(req.clone()); + } else { + return GenTicketStatus::Unknown; + } + } + + pub fn unlock_tx_status(&self, ticket_id: &TicketId) -> ReleaseTokenStatus { + let seq = self.ticket_id_seq_indexer.get(ticket_id).cloned(); + + if seq.is_none() { + return ReleaseTokenStatus::Unknown; + } + let seq = seq.unwrap(); + if let Some(status) = self.flight_unlock_ticket_map.get(&seq).cloned() { + if status.success { + let txid = status.txid; + return ReleaseTokenStatus::Submitted(txid.to_string()); + } else { + return ReleaseTokenStatus::Unknown; + } + } + if let Some(tx) = self.finalized_unlock_ticket_results_map.get(&seq) { + let txid = tx.txid.to_string(); + return ReleaseTokenStatus::Confirmed(txid); + } + ReleaseTokenStatus::Pending + } + + pub fn chain_params(&self) -> &'static ChainParams { + chain_from_key_bits(self.doge_chain) + } + + pub fn get_address(&self, dest: Destination) -> Result<(Address, Vec), CustomsError> { + let pk = self + .ecdsa_public_key + .clone() + .ok_or(CustomsError::ECDSAPublicKeyNotFound)?; + + let pk = derive_public_key(&pk, dest.derivation_path()); + Ok((script::p2pkh_address(&pk.public_key, self.chain_params())?, pk.public_key)) + } + + pub fn save_utxo(&mut self, ticket_request: LockTicketRequest) -> Result<(), CustomsError> { + let transaction: Transaction = deserialize_hex(&ticket_request.transaction_hex).map_err(wrap_to_customs_error)?; + let destination = Destination::new(ticket_request.target_chain_id.clone(), ticket_request.receiver.clone(), None); + let destination_address = self.get_address(destination.clone())?.0.to_string(); + for (i, tx_out) in transaction.output.iter().enumerate() { + if let Some(tx_out_address) = tx_out.get_mainnet_address() { + if tx_out_address == destination_address { + self.deposited_utxo.push( + (Utxo { + txid: ticket_request.txid.clone(), + vout: i as u32, + value: tx_out.value, + }, + destination.clone(), + )); + } + } + } + + Ok(()) + } +} + +pub async fn init_ecdsa_public_key() -> Result { + if let Some(pub_key) = read_state(|s| s.ecdsa_public_key.clone()) { + return Ok(pub_key); + }; + let key_name = read_state(|s| s.ecdsa_key_name.clone()); + let pub_key = crate::management::ecdsa_public_key(key_name, DerivationPath::new(vec![])) + .await + .unwrap_or_else(|e| ic_cdk::trap(&format!("failed to retrieve ECDSA public key: {e}"))); + mutate_state(|s| { + s.ecdsa_public_key = Some(pub_key.clone()); + Ok(()) + })?; + Ok(pub_key) +} + +pub fn finalization_time_estimate( + min_confirmations: u32, +) -> Duration { + Duration::from_nanos( + (min_confirmations + 1) as u64 * 1 * MIN_NANOS + ) +} + +pub fn mutate_state(f: F) -> R +where + F: FnOnce(&mut DogeState) -> R, +{ + STATE.with(|s| f(s.borrow_mut().as_mut().expect("State not initialized!"))) +} + +pub fn read_state(f: F) -> R +where + F: FnOnce(&DogeState) -> R, +{ + STATE.with(|s| f(s.borrow().as_ref().expect("State not initialized!"))) +} + +/// Replaces the current state. +pub fn replace_state(state: DogeState) { + STATE.with(|s| { + *s.borrow_mut() = Some(state); + }); +} diff --git a/customs/doge/src/tasks.rs b/customs/doge/src/tasks.rs new file mode 100644 index 00000000..699c8ef6 --- /dev/null +++ b/customs/doge/src/tasks.rs @@ -0,0 +1,32 @@ +use std::time::Duration; + +use crate::dogeoin_to_custom::finalize_lock_ticket_task; +use ic_cdk_timers::set_timer_interval; + +use crate::constants::*; +use crate::custom_to_dogecoin::{finalize_unlock_tickets_task, submit_unlock_tickets_task}; +use crate::hub_to_custom::{fetch_hub_directive_task, fetch_hub_ticket_task}; + +pub fn start_tasks() { + set_timer_interval( + Duration::from_secs(FETCH_HUB_TICKET_INTERVAL), + fetch_hub_ticket_task, + ); + set_timer_interval( + Duration::from_secs(FETCH_HUB_DIRECTIVE_INTERVAL), + fetch_hub_directive_task, + ); + set_timer_interval( + Duration::from_secs(SUBMIT_UNLOCK_TICKETS_INTERVAL), + submit_unlock_tickets_task, + ); + set_timer_interval( + Duration::from_secs(FINALIZE_LOCK_TICKET_INTERVAL), + finalize_lock_ticket_task, + ); + set_timer_interval( + Duration::from_secs(FINALIZE_UNLOCK_TICKET_INTERVAL), + finalize_unlock_tickets_task, + ); + +} diff --git a/customs/doge/src/types.rs b/customs/doge/src/types.rs new file mode 100644 index 00000000..5c314b73 --- /dev/null +++ b/customs/doge/src/types.rs @@ -0,0 +1,350 @@ +use std::borrow::Cow; + +use bitcoin::consensus::{Decodable, Encodable, ReadExt}; +use candid::{CandidType, Deserialize, Nat}; +use ic_cdk::api::management_canister::http_request::{ + http_request, CanisterHttpRequestArgument, HttpResponse, TransformContext, +}; +use ic_stable_structures::{storable::Bound, Storable}; +use omnity_types::ic_log::{ERROR, INFO, WARNING}; +use serde::Serialize; +// use std::str::FromStr; +use hex::prelude::*; + +use ic_canister_log::log; +use omnity_types::{Token, TokenId}; + +use crate::{ + doge::{rpc::get_raw_transaction_by_rpc, transaction::Transaction}, + errors::CustomsError, +}; +use bitcoin::hashes::{sha256d, Hash}; +use serde_bytes::ByteArray; + +pub type ECDSAPublicKey = ic_cdk::api::management_canister::ecdsa::EcdsaPublicKeyResponse; + +#[derive(CandidType, PartialEq, Eq, Clone, Default, Deserialize, Serialize, PartialOrd, Ord)] +pub struct Txid(pub ByteArray<32>); + +impl std::str::FromStr for Txid { + type Err = String; + + fn from_str(s: &str) -> Result { + let h = sha256d::Hash::from_str(s).map_err(|_| "invalid Txid")?; + Ok(Self(h.to_byte_array().into())) + } +} + +impl Storable for Txid { + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Cow::Owned(bincode::serialize(self).unwrap()) + } + + fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { + bincode::deserialize(bytes.as_ref()).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl std::fmt::Debug for Txid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + sha256d::Hash::from_bytes_ref(&self.0).fmt(f) + } +} + +impl std::fmt::Display for Txid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + sha256d::Hash::from_bytes_ref(&self.0).fmt(f) + } +} + +impl From> for Txid { + fn from(val: ByteArray<32>) -> Self { + Self(val) + } +} + +impl From<[u8; 32]> for Txid { + fn from(val: [u8; 32]) -> Self { + Self(val.into()) + } +} + +impl From for Txid { + fn from(txid: crate::doge::transaction::Txid) -> Self { + Self((*txid).into()) + } +} + +impl From for crate::doge::transaction::Txid { + fn from(txid: Txid) -> Self { + Self::from_byte_array(*txid.0) + } +} + +#[derive(Serialize, CandidType, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Destination { + pub target_chain_id: String, + pub receiver: String, + pub token: Option, +} + +impl Destination { + pub fn new(target_chain_id: String, receiver: String, token: Option) -> Self { + Destination { + target_chain_id, + receiver, + token, + } + } + + pub fn change_address() -> Destination { + Destination::new(String::default(), String::default(), Option::None) + } + + pub fn fee_payment_address() -> Destination { + Destination::new( + "fee_payment".to_string(), + "fee_payment".to_string(), + Option::None, + ) + } + + #[inline] + pub fn effective_token(&self) -> String { + self.token.clone().unwrap_or(String::new()) + } + + pub fn derivation_path(&self) -> Vec> { + const SCHEMA_V1: u8 = 1; + vec![ + vec![SCHEMA_V1], + self.target_chain_id.as_bytes().to_vec(), + self.receiver.as_bytes().to_vec(), + self.effective_token().as_bytes().to_vec(), + ] + } +} + +#[derive(CandidType, Clone, Debug, Serialize, Deserialize)] +pub struct TokenResp { + pub token_id: TokenId, + pub symbol: String, + pub decimals: u8, + pub icon: Option, +} + +impl From for TokenResp { + fn from(value: Token) -> Self { + TokenResp { + token_id: value.token_id, + symbol: value.symbol, + decimals: value.decimals, + icon: value.icon, + } + } +} + +#[derive(candid::CandidType, Clone, Debug, PartialEq, Eq, Deserialize)] +pub enum ReleaseTokenStatus { + /// The custom has no data for this request. + /// The request id is either invalid or too old. + Unknown, + /// The request is in the batch queue. + Pending, + /// Waiting for a signature on a transaction satisfy this request. + Signing, + /// Sending the transaction satisfying this request. + Sending(String), + /// Awaiting for confirmations on the transaction satisfying this request. + Submitted(String), + /// Confirmed a transaction satisfying this request. + Confirmed(String), +} + +#[derive(CandidType, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum GenTicketStatus { + /// The custom has no data for this request. + /// The request is either invalid or too old. + Unknown, + /// The request is in the queue. + Pending(LockTicketRequest), + Confirmed(LockTicketRequest), + Finalized(LockTicketRequest), +} + +#[derive(candid::CandidType, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct LockTicketRequest { + pub target_chain_id: String, + pub receiver: String, + pub token_id: TokenId, + pub amount: String, + pub txid: Txid, + pub received_at: u64, + pub transaction_hex: String, +} + +impl Storable for LockTicketRequest { + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Cow::Owned(bincode::serialize(self).unwrap()) + } + + fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { + bincode::deserialize(bytes.as_ref()).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +pub fn err_string(err: impl std::fmt::Display) -> String { + err.to_string() +} + +pub fn wrap_to_customs_error(err: impl std::fmt::Display) -> CustomsError { + CustomsError::CustomError(err.to_string()) +} + +pub fn serialize_hex(v: &T) -> String { + let mut buf = Vec::new(); + v.consensus_encode(&mut buf) + .expect("serialize_hex: encode failed"); + buf.to_lower_hex_string() +} + +pub fn deserialize_hex(hex: &str) -> Result { + let data = Vec::from_hex(hex).map_err(err_string)?; + let mut reader = &data[..]; + let object = Decodable::consensus_decode_from_finite_reader(&mut reader).map_err(err_string)?; + if reader.read_u8().is_ok() { + Err("decode_hex: data not consumed entirely".to_string()) + } else { + Ok(object) + } +} + +pub async fn http_request_with_retry( + mut request: CanisterHttpRequestArgument, +) -> Result { + request.transform = Some(TransformContext::from_name("transform".to_owned(), vec![])); + + for _ in 0..3 { + let response = match http_request(request.clone(), 60_000_000_000).await { + Ok((response,)) => response, + Err(e) => { + log!(ERROR, "http request error, request: {:?} \n, error {:?}",request, e); + continue; + } + }; + + log!( + INFO, + "httpoutcall request:{:?} response: {:?}", + request, + response + ); + if response.status == Nat::from(200u64) { + return Ok(response); + } else { + log!(WARNING, "http request error: {:?}", response); + } + } + Err(CustomsError::HttpOutExceedRetryLimit) +} + +#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)] +pub struct Utxo { + pub txid: Txid, + pub vout: u32, + pub value: u64, +} + +impl From for crate::doge::transaction::TxIn { + fn from(val: Utxo) -> Self { + Self::with_outpoint(crate::doge::transaction::OutPoint { + txid: val.txid.into(), + vout: val.vout, + }) + } +} + +#[derive(CandidType, Clone, Debug, Default, Deserialize, Serialize)] +pub struct RpcConfig { + pub url: String, + pub api_key: Option, +} + +impl From for crate::doge::rpc::DogeRpc { + fn from(val: RpcConfig) -> Self { + Self { + url: val.url, + api_key: val.api_key, + } + } +} + +#[derive(CandidType, Clone, Debug, Deserialize, Serialize, Default)] +pub struct MultiRpcConfig { + pub rpc_list: Vec, + pub minimum_response_count: u32, +} + +impl MultiRpcConfig { + pub fn check_config_valid(&self) -> Result<(), CustomsError> { + if self.minimum_response_count == 0 { + return Err(CustomsError::CustomError( + "minimum_response_count should be greater than 0".to_string(), + )); + } + if self.rpc_list.len() < self.minimum_response_count as usize { + return Err(CustomsError::CustomError( + "rpc_list length should be greater than minimum_response_count".to_string(), + )); + } + Ok(()) + } + + pub async fn get_raw_transaction(&self, txid: &str) -> Result { + self.check_config_valid()?; + + let mut fut = Vec::with_capacity(self.rpc_list.len()); + for rpc_config in self.rpc_list.iter() { + // let doge_rpc = DogeRpc::from(rpc_url.clone()); + fut.push(get_raw_transaction_by_rpc(txid, rpc_config.clone())); + } + + let res = futures::future::join_all(fut).await; + + self.valid_and_get_transaction(res) + } + + fn valid_and_get_transaction( + &self, + res: Vec>, + ) -> Result { + let success_res = res + .iter() + .filter_map(|r| r.as_ref().ok()) + .collect::>(); + self.check_config_valid()?; + if success_res.len() < self.minimum_response_count as usize { + return Err(CustomsError::CustomError(format!( + "Success res count({}) less than minimum_response_count: {}", + success_res.len(), + self.minimum_response_count + ))); + } + + for i in 1..success_res.len() { + if success_res[i] != success_res[i - 1] { + return Err(CustomsError::CustomError(format!( + "Success res not equal: {:?} != {:?}", + success_res[i], + success_res[i - 1] + ))); + } + } + + Ok(success_res[0].clone()) + } +} diff --git a/dfx.json b/dfx.json index 2e747db6..bfd215d3 100644 --- a/dfx.json +++ b/dfx.json @@ -35,6 +35,18 @@ } ] }, + "doge_customs": { + "gzip": true, + "candid": "customs/doge/doge_customs.did", + "package": "doge_customs", + "type": "custom", + "wasm": "target/wasm32-unknown-unknown/release/doge_customs.wasm", + "metadata": [ + { + "name": "candid:service" + } + ] + }, "icp_customs": { "gzip": true, "candid": "customs/icp/icp_customs.did",