├── .gitignore ├── README.md ├── abi ├── depositor │ ├── depositor.go │ └── depositor.json ├── erc20 │ ├── erc20.go │ └── erc20.json ├── owlto │ ├── owlto20.go │ └── owlto20.json └── owlto_sol_transfer │ ├── TransferLamports.go │ ├── TransferLamports_test.go │ ├── TransferSplTokens.go │ ├── TransferSplTokens_test.go │ ├── accounts.go │ ├── instructions.go │ ├── owlto_sol.json │ ├── testing_utils.go │ └── types.go ├── alert ├── common.go ├── lark.go └── larknotice.go ├── apollosdk └── apolloconfig.go ├── asynccache ├── README.md ├── asynccache.go ├── asynccache_test.go └── atomic_error.go ├── config └── config.go ├── contract └── solana │ ├── contract.rs │ └── test.ts ├── convert └── convert.go ├── errors └── error.go ├── go.mod ├── go.sum ├── json_extract └── json_extract.go ├── loader ├── account.go ├── bridge_fee.go ├── chain_info.go ├── channel_commission_ratio.go ├── circle_cctp_chain.go ├── dst_tx.go ├── dtc.go ├── exchange_info.go ├── lp_info.go ├── maker_address.go ├── popular_list.go ├── src_tx.go ├── token_info.go └── update_price.go ├── log └── logger.go ├── network └── http.go ├── pointer └── pointer.go ├── rpc ├── bitcoin.go ├── evm.go ├── rpc.go ├── rpc_test.go ├── solana.go ├── starknet.go └── zkslite.go ├── system ├── dir.go └── signal.go ├── task └── task.go ├── telemetry ├── metrics.go └── wrapper.go ├── txn ├── evm │ ├── common.go │ └── erc20.go └── solana │ ├── common.go │ └── spl.go └── util ├── address.go ├── common.go ├── generate.go └── page.go /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | utils-go 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # utils-go -------------------------------------------------------------------------------- /abi/depositor/depositor.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "inputs": [], 9 | "name": "AmountError", 10 | "type": "error" 11 | }, 12 | { 13 | "inputs": [], 14 | "name": "CallError", 15 | "type": "error" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "LengthError", 20 | "type": "error" 21 | }, 22 | { 23 | "inputs": [], 24 | "name": "NotOwnerError", 25 | "type": "error" 26 | }, 27 | { 28 | "inputs": [], 29 | "name": "TargetError", 30 | "type": "error" 31 | }, 32 | { 33 | "inputs": [], 34 | "name": "ZeroAddressError", 35 | "type": "error" 36 | }, 37 | { 38 | "anonymous": false, 39 | "inputs": [ 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "user", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": true, 48 | "internalType": "address", 49 | "name": "token", 50 | "type": "address" 51 | }, 52 | { 53 | "indexed": true, 54 | "internalType": "address", 55 | "name": "maker", 56 | "type": "address" 57 | }, 58 | { 59 | "indexed": false, 60 | "internalType": "string", 61 | "name": "target", 62 | "type": "string" 63 | }, 64 | { 65 | "indexed": false, 66 | "internalType": "uint256", 67 | "name": "amount", 68 | "type": "uint256" 69 | }, 70 | { 71 | "indexed": false, 72 | "internalType": "uint256", 73 | "name": "destination", 74 | "type": "uint256" 75 | }, 76 | { 77 | "indexed": false, 78 | "internalType": "uint256", 79 | "name": "channel", 80 | "type": "uint256" 81 | }, 82 | { 83 | "indexed": false, 84 | "internalType": "uint256", 85 | "name": "timestamp", 86 | "type": "uint256" 87 | } 88 | ], 89 | "name": "Deposit", 90 | "type": "event" 91 | }, 92 | { 93 | "inputs": [ 94 | { 95 | "internalType": "string", 96 | "name": "target", 97 | "type": "string" 98 | }, 99 | { 100 | "internalType": "address", 101 | "name": "token", 102 | "type": "address" 103 | }, 104 | { 105 | "internalType": "address", 106 | "name": "maker", 107 | "type": "address" 108 | }, 109 | { 110 | "internalType": "uint256", 111 | "name": "amount", 112 | "type": "uint256" 113 | }, 114 | { 115 | "internalType": "uint256", 116 | "name": "destination", 117 | "type": "uint256" 118 | }, 119 | { 120 | "internalType": "uint256", 121 | "name": "channel", 122 | "type": "uint256" 123 | } 124 | ], 125 | "name": "deposit", 126 | "outputs": [], 127 | "stateMutability": "payable", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [], 132 | "name": "isOwltoDepositor", 133 | "outputs": [ 134 | { 135 | "internalType": "bool", 136 | "name": "", 137 | "type": "bool" 138 | } 139 | ], 140 | "stateMutability": "pure", 141 | "type": "function" 142 | }, 143 | { 144 | "stateMutability": "payable", 145 | "type": "receive" 146 | } 147 | ] -------------------------------------------------------------------------------- /abi/erc20/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] -------------------------------------------------------------------------------- /abi/owlto/owlto20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "inputs": [], 9 | "name": "AmountError", 10 | "type": "error" 11 | }, 12 | { 13 | "inputs": [], 14 | "name": "LengthError", 15 | "type": "error" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "NotOwnerError", 20 | "type": "error" 21 | }, 22 | { 23 | "inputs": [], 24 | "name": "ZeroAddressError", 25 | "type": "error" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "user", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "token", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": true, 44 | "internalType": "address", 45 | "name": "maker", 46 | "type": "address" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "string", 51 | "name": "target", 52 | "type": "string" 53 | }, 54 | { 55 | "indexed": false, 56 | "internalType": "uint256", 57 | "name": "amount", 58 | "type": "uint256" 59 | }, 60 | { 61 | "indexed": false, 62 | "internalType": "uint256", 63 | "name": "timestamp", 64 | "type": "uint256" 65 | } 66 | ], 67 | "name": "Deposit", 68 | "type": "event" 69 | }, 70 | { 71 | "inputs": [], 72 | "name": "isOwltoTransfer", 73 | "outputs": [ 74 | { 75 | "internalType": "bool", 76 | "name": "", 77 | "type": "bool" 78 | } 79 | ], 80 | "stateMutability": "pure", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [ 85 | { 86 | "internalType": "string", 87 | "name": "target", 88 | "type": "string" 89 | }, 90 | { 91 | "internalType": "address", 92 | "name": "ercToken", 93 | "type": "address" 94 | }, 95 | { 96 | "internalType": "address", 97 | "name": "maker", 98 | "type": "address" 99 | }, 100 | { 101 | "internalType": "uint256", 102 | "name": "amount", 103 | "type": "uint256" 104 | } 105 | ], 106 | "name": "transfer", 107 | "outputs": [], 108 | "stateMutability": "payable", 109 | "type": "function" 110 | }, 111 | { 112 | "stateMutability": "payable", 113 | "type": "receive" 114 | } 115 | ] -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/TransferLamports.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "errors" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ag_format "github.com/gagliardetto/solana-go/text/format" 10 | ag_treeout "github.com/gagliardetto/treeout" 11 | ) 12 | 13 | // TransferLamports is the `transferLamports` instruction. 14 | type TransferLamports struct { 15 | TransferData *TransferData 16 | 17 | // [0] = [WRITE, SIGNER] from 18 | // 19 | // [1] = [WRITE] to 20 | // 21 | // [2] = [] systemProgram 22 | ag_solanago.AccountMetaSlice `bin:"-"` 23 | } 24 | 25 | // NewTransferLamportsInstructionBuilder creates a new `TransferLamports` instruction builder. 26 | func NewTransferLamportsInstructionBuilder() *TransferLamports { 27 | nd := &TransferLamports{ 28 | AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), 29 | } 30 | return nd 31 | } 32 | 33 | // SetTransferData sets the "transferData" parameter. 34 | func (inst *TransferLamports) SetTransferData(transferData TransferData) *TransferLamports { 35 | inst.TransferData = &transferData 36 | return inst 37 | } 38 | 39 | // SetFromAccount sets the "from" account. 40 | func (inst *TransferLamports) SetFromAccount(from ag_solanago.PublicKey) *TransferLamports { 41 | inst.AccountMetaSlice[0] = ag_solanago.Meta(from).WRITE().SIGNER() 42 | return inst 43 | } 44 | 45 | // GetFromAccount gets the "from" account. 46 | func (inst *TransferLamports) GetFromAccount() *ag_solanago.AccountMeta { 47 | return inst.AccountMetaSlice.Get(0) 48 | } 49 | 50 | // SetToAccount sets the "to" account. 51 | func (inst *TransferLamports) SetToAccount(to ag_solanago.PublicKey) *TransferLamports { 52 | inst.AccountMetaSlice[1] = ag_solanago.Meta(to).WRITE() 53 | return inst 54 | } 55 | 56 | // GetToAccount gets the "to" account. 57 | func (inst *TransferLamports) GetToAccount() *ag_solanago.AccountMeta { 58 | return inst.AccountMetaSlice.Get(1) 59 | } 60 | 61 | // SetSystemProgramAccount sets the "systemProgram" account. 62 | func (inst *TransferLamports) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *TransferLamports { 63 | inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) 64 | return inst 65 | } 66 | 67 | // GetSystemProgramAccount gets the "systemProgram" account. 68 | func (inst *TransferLamports) GetSystemProgramAccount() *ag_solanago.AccountMeta { 69 | return inst.AccountMetaSlice.Get(2) 70 | } 71 | 72 | func (inst TransferLamports) Build() *Instruction { 73 | return &Instruction{BaseVariant: ag_binary.BaseVariant{ 74 | Impl: inst, 75 | TypeID: Instruction_TransferLamports, 76 | }} 77 | } 78 | 79 | // ValidateAndBuild validates the instruction parameters and accounts; 80 | // if there is a validation error, it returns the error. 81 | // Otherwise, it builds and returns the instruction. 82 | func (inst TransferLamports) ValidateAndBuild() (*Instruction, error) { 83 | if err := inst.Validate(); err != nil { 84 | return nil, err 85 | } 86 | return inst.Build(), nil 87 | } 88 | 89 | func (inst *TransferLamports) Validate() error { 90 | // Check whether all (required) parameters are set: 91 | { 92 | if inst.TransferData == nil { 93 | return errors.New("TransferData parameter is not set") 94 | } 95 | } 96 | 97 | // Check whether all (required) accounts are set: 98 | { 99 | if inst.AccountMetaSlice[0] == nil { 100 | return errors.New("accounts.From is not set") 101 | } 102 | if inst.AccountMetaSlice[1] == nil { 103 | return errors.New("accounts.To is not set") 104 | } 105 | if inst.AccountMetaSlice[2] == nil { 106 | return errors.New("accounts.SystemProgram is not set") 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | func (inst *TransferLamports) EncodeToTree(parent ag_treeout.Branches) { 113 | parent.Child(ag_format.Program(ProgramName, ProgramID)). 114 | // 115 | ParentFunc(func(programBranch ag_treeout.Branches) { 116 | programBranch.Child(ag_format.Instruction("TransferLamports")). 117 | // 118 | ParentFunc(func(instructionBranch ag_treeout.Branches) { 119 | 120 | // Parameters of the instruction: 121 | instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { 122 | paramsBranch.Child(ag_format.Param("TransferData", *inst.TransferData)) 123 | }) 124 | 125 | // Accounts of the instruction: 126 | instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { 127 | accountsBranch.Child(ag_format.Meta(" from", inst.AccountMetaSlice.Get(0))) 128 | accountsBranch.Child(ag_format.Meta(" to", inst.AccountMetaSlice.Get(1))) 129 | accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(2))) 130 | }) 131 | }) 132 | }) 133 | } 134 | 135 | func (obj TransferLamports) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 136 | // Serialize `TransferData` param: 137 | err = encoder.Encode(obj.TransferData) 138 | if err != nil { 139 | return err 140 | } 141 | return nil 142 | } 143 | func (obj *TransferLamports) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 144 | // Deserialize `TransferData`: 145 | err = decoder.Decode(&obj.TransferData) 146 | if err != nil { 147 | return err 148 | } 149 | return nil 150 | } 151 | 152 | // NewTransferLamportsInstruction declares a new TransferLamports instruction with the provided parameters and accounts. 153 | func NewTransferLamportsInstruction( 154 | // Parameters: 155 | transferData TransferData, 156 | // Accounts: 157 | from ag_solanago.PublicKey, 158 | to ag_solanago.PublicKey, 159 | systemProgram ag_solanago.PublicKey) *TransferLamports { 160 | return NewTransferLamportsInstructionBuilder(). 161 | SetTransferData(transferData). 162 | SetFromAccount(from). 163 | SetToAccount(to). 164 | SetSystemProgramAccount(systemProgram) 165 | } 166 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/TransferLamports_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_TransferLamports(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("TransferLamports"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(TransferLamports) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(TransferLamports) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/TransferSplTokens.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "errors" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ag_format "github.com/gagliardetto/solana-go/text/format" 10 | ag_treeout "github.com/gagliardetto/treeout" 11 | ) 12 | 13 | // TransferSplTokens is the `transferSplTokens` instruction. 14 | type TransferSplTokens struct { 15 | TransferData *TransferData 16 | 17 | // [0] = [SIGNER] from 18 | // 19 | // [1] = [WRITE] fromAta 20 | // 21 | // [2] = [WRITE] toAta 22 | // 23 | // [3] = [] tokenProgram 24 | ag_solanago.AccountMetaSlice `bin:"-"` 25 | } 26 | 27 | // NewTransferSplTokensInstructionBuilder creates a new `TransferSplTokens` instruction builder. 28 | func NewTransferSplTokensInstructionBuilder() *TransferSplTokens { 29 | nd := &TransferSplTokens{ 30 | AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4), 31 | } 32 | return nd 33 | } 34 | 35 | // SetTransferData sets the "transferData" parameter. 36 | func (inst *TransferSplTokens) SetTransferData(transferData TransferData) *TransferSplTokens { 37 | inst.TransferData = &transferData 38 | return inst 39 | } 40 | 41 | // SetFromAccount sets the "from" account. 42 | func (inst *TransferSplTokens) SetFromAccount(from ag_solanago.PublicKey) *TransferSplTokens { 43 | inst.AccountMetaSlice[0] = ag_solanago.Meta(from).SIGNER() 44 | return inst 45 | } 46 | 47 | // GetFromAccount gets the "from" account. 48 | func (inst *TransferSplTokens) GetFromAccount() *ag_solanago.AccountMeta { 49 | return inst.AccountMetaSlice.Get(0) 50 | } 51 | 52 | // SetFromAtaAccount sets the "fromAta" account. 53 | func (inst *TransferSplTokens) SetFromAtaAccount(fromAta ag_solanago.PublicKey) *TransferSplTokens { 54 | inst.AccountMetaSlice[1] = ag_solanago.Meta(fromAta).WRITE() 55 | return inst 56 | } 57 | 58 | // GetFromAtaAccount gets the "fromAta" account. 59 | func (inst *TransferSplTokens) GetFromAtaAccount() *ag_solanago.AccountMeta { 60 | return inst.AccountMetaSlice.Get(1) 61 | } 62 | 63 | // SetToAtaAccount sets the "toAta" account. 64 | func (inst *TransferSplTokens) SetToAtaAccount(toAta ag_solanago.PublicKey) *TransferSplTokens { 65 | inst.AccountMetaSlice[2] = ag_solanago.Meta(toAta).WRITE() 66 | return inst 67 | } 68 | 69 | // GetToAtaAccount gets the "toAta" account. 70 | func (inst *TransferSplTokens) GetToAtaAccount() *ag_solanago.AccountMeta { 71 | return inst.AccountMetaSlice.Get(2) 72 | } 73 | 74 | // SetTokenProgramAccount sets the "tokenProgram" account. 75 | func (inst *TransferSplTokens) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *TransferSplTokens { 76 | inst.AccountMetaSlice[3] = ag_solanago.Meta(tokenProgram) 77 | return inst 78 | } 79 | 80 | // GetTokenProgramAccount gets the "tokenProgram" account. 81 | func (inst *TransferSplTokens) GetTokenProgramAccount() *ag_solanago.AccountMeta { 82 | return inst.AccountMetaSlice.Get(3) 83 | } 84 | 85 | func (inst TransferSplTokens) Build() *Instruction { 86 | return &Instruction{BaseVariant: ag_binary.BaseVariant{ 87 | Impl: inst, 88 | TypeID: Instruction_TransferSplTokens, 89 | }} 90 | } 91 | 92 | // ValidateAndBuild validates the instruction parameters and accounts; 93 | // if there is a validation error, it returns the error. 94 | // Otherwise, it builds and returns the instruction. 95 | func (inst TransferSplTokens) ValidateAndBuild() (*Instruction, error) { 96 | if err := inst.Validate(); err != nil { 97 | return nil, err 98 | } 99 | return inst.Build(), nil 100 | } 101 | 102 | func (inst *TransferSplTokens) Validate() error { 103 | // Check whether all (required) parameters are set: 104 | { 105 | if inst.TransferData == nil { 106 | return errors.New("TransferData parameter is not set") 107 | } 108 | } 109 | 110 | // Check whether all (required) accounts are set: 111 | { 112 | if inst.AccountMetaSlice[0] == nil { 113 | return errors.New("accounts.From is not set") 114 | } 115 | if inst.AccountMetaSlice[1] == nil { 116 | return errors.New("accounts.FromAta is not set") 117 | } 118 | if inst.AccountMetaSlice[2] == nil { 119 | return errors.New("accounts.ToAta is not set") 120 | } 121 | if inst.AccountMetaSlice[3] == nil { 122 | return errors.New("accounts.TokenProgram is not set") 123 | } 124 | } 125 | return nil 126 | } 127 | 128 | func (inst *TransferSplTokens) EncodeToTree(parent ag_treeout.Branches) { 129 | parent.Child(ag_format.Program(ProgramName, ProgramID)). 130 | // 131 | ParentFunc(func(programBranch ag_treeout.Branches) { 132 | programBranch.Child(ag_format.Instruction("TransferSplTokens")). 133 | // 134 | ParentFunc(func(instructionBranch ag_treeout.Branches) { 135 | 136 | // Parameters of the instruction: 137 | instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) { 138 | paramsBranch.Child(ag_format.Param("TransferData", *inst.TransferData)) 139 | }) 140 | 141 | // Accounts of the instruction: 142 | instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) { 143 | accountsBranch.Child(ag_format.Meta(" from", inst.AccountMetaSlice.Get(0))) 144 | accountsBranch.Child(ag_format.Meta(" fromAta", inst.AccountMetaSlice.Get(1))) 145 | accountsBranch.Child(ag_format.Meta(" toAta", inst.AccountMetaSlice.Get(2))) 146 | accountsBranch.Child(ag_format.Meta("tokenProgram", inst.AccountMetaSlice.Get(3))) 147 | }) 148 | }) 149 | }) 150 | } 151 | 152 | func (obj TransferSplTokens) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 153 | // Serialize `TransferData` param: 154 | err = encoder.Encode(obj.TransferData) 155 | if err != nil { 156 | return err 157 | } 158 | return nil 159 | } 160 | func (obj *TransferSplTokens) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 161 | // Deserialize `TransferData`: 162 | err = decoder.Decode(&obj.TransferData) 163 | if err != nil { 164 | return err 165 | } 166 | return nil 167 | } 168 | 169 | // NewTransferSplTokensInstruction declares a new TransferSplTokens instruction with the provided parameters and accounts. 170 | func NewTransferSplTokensInstruction( 171 | // Parameters: 172 | transferData TransferData, 173 | // Accounts: 174 | from ag_solanago.PublicKey, 175 | fromAta ag_solanago.PublicKey, 176 | toAta ag_solanago.PublicKey, 177 | tokenProgram ag_solanago.PublicKey) *TransferSplTokens { 178 | return NewTransferSplTokensInstructionBuilder(). 179 | SetTransferData(transferData). 180 | SetFromAccount(from). 181 | SetFromAtaAccount(fromAta). 182 | SetToAtaAccount(toAta). 183 | SetTokenProgramAccount(tokenProgram) 184 | } 185 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/TransferSplTokens_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_TransferSplTokens(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("TransferSplTokens"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(TransferSplTokens) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(TransferSplTokens) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/accounts.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/instructions.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | ag_spew "github.com/davecgh/go-spew/spew" 9 | ag_binary "github.com/gagliardetto/binary" 10 | ag_solanago "github.com/gagliardetto/solana-go" 11 | ag_text "github.com/gagliardetto/solana-go/text" 12 | ag_treeout "github.com/gagliardetto/treeout" 13 | ) 14 | 15 | var ProgramID ag_solanago.PublicKey 16 | 17 | func SetProgramID(pubkey ag_solanago.PublicKey) { 18 | ProgramID = pubkey 19 | ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) 20 | } 21 | 22 | const ProgramName = "OwltoSolTransfer" 23 | 24 | func init() { 25 | if !ProgramID.IsZero() { 26 | ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) 27 | } 28 | } 29 | 30 | var ( 31 | Instruction_TransferLamports = ag_binary.TypeID([8]byte{62, 53, 201, 68, 102, 134, 83, 103}) 32 | 33 | Instruction_TransferSplTokens = ag_binary.TypeID([8]byte{20, 18, 113, 207, 62, 6, 205, 80}) 34 | ) 35 | 36 | // InstructionIDToName returns the name of the instruction given its ID. 37 | func InstructionIDToName(id ag_binary.TypeID) string { 38 | switch id { 39 | case Instruction_TransferLamports: 40 | return "TransferLamports" 41 | case Instruction_TransferSplTokens: 42 | return "TransferSplTokens" 43 | default: 44 | return "" 45 | } 46 | } 47 | 48 | type Instruction struct { 49 | ag_binary.BaseVariant 50 | } 51 | 52 | func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) { 53 | if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { 54 | enToTree.EncodeToTree(parent) 55 | } else { 56 | parent.Child(ag_spew.Sdump(inst)) 57 | } 58 | } 59 | 60 | var InstructionImplDef = ag_binary.NewVariantDefinition( 61 | ag_binary.AnchorTypeIDEncoding, 62 | []ag_binary.VariantType{ 63 | { 64 | "transfer_lamports", (*TransferLamports)(nil), 65 | }, 66 | { 67 | "transfer_spl_tokens", (*TransferSplTokens)(nil), 68 | }, 69 | }, 70 | ) 71 | 72 | func (inst *Instruction) ProgramID() ag_solanago.PublicKey { 73 | return ProgramID 74 | } 75 | 76 | func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { 77 | return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() 78 | } 79 | 80 | func (inst *Instruction) Data() ([]byte, error) { 81 | buf := new(bytes.Buffer) 82 | if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil { 83 | return nil, fmt.Errorf("unable to encode instruction: %w", err) 84 | } 85 | return buf.Bytes(), nil 86 | } 87 | 88 | func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { 89 | return encoder.Encode(inst.Impl, option) 90 | } 91 | 92 | func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { 93 | return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) 94 | } 95 | 96 | func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error { 97 | err := encoder.WriteBytes(inst.TypeID.Bytes(), false) 98 | if err != nil { 99 | return fmt.Errorf("unable to write variant type: %w", err) 100 | } 101 | return encoder.Encode(inst.Impl) 102 | } 103 | 104 | func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { 105 | inst, err := DecodeInstruction(accounts, data) 106 | if err != nil { 107 | return nil, err 108 | } 109 | return inst, nil 110 | } 111 | 112 | func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { 113 | inst := new(Instruction) 114 | if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil { 115 | return nil, fmt.Errorf("unable to decode instruction: %w", err) 116 | } 117 | if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { 118 | err := v.SetAccounts(accounts) 119 | if err != nil { 120 | return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) 121 | } 122 | } 123 | return inst, nil 124 | } 125 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/owlto_sol.json: -------------------------------------------------------------------------------- 1 | {"version":"0.1.0","name":"owlto_sol_transfer","instructions":[{"name":"transferLamports","accounts":[{"name":"from","isMut":true,"isSigner":true},{"name":"to","isMut":true,"isSigner":false},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"transferData","type":{"defined":"TransferData"}}]},{"name":"transferSplTokens","accounts":[{"name":"from","isMut":false,"isSigner":true},{"name":"fromAta","isMut":true,"isSigner":false},{"name":"toAta","isMut":true,"isSigner":false},{"name":"tokenProgram","isMut":false,"isSigner":false}],"args":[{"name":"transferData","type":{"defined":"TransferData"}}]}],"types":[{"name":"TransferData","type":{"kind":"struct","fields":[{"name":"amount","type":"u64"},{"name":"targetAddr","type":"string"}]}}]} -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/testing_utils.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | ag_binary "github.com/gagliardetto/binary" 9 | ) 10 | 11 | func encodeT(data interface{}, buf *bytes.Buffer) error { 12 | if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil { 13 | return fmt.Errorf("unable to encode instruction: %w", err) 14 | } 15 | return nil 16 | } 17 | 18 | func decodeT(dst interface{}, data []byte) error { 19 | return ag_binary.NewBorshDecoder(data).Decode(dst) 20 | } 21 | -------------------------------------------------------------------------------- /abi/owlto_sol_transfer/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package owlto_sol_transfer 4 | 5 | import ag_binary "github.com/gagliardetto/binary" 6 | 7 | type TransferData struct { 8 | Amount uint64 9 | TargetAddr string 10 | } 11 | 12 | func (obj TransferData) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 13 | // Serialize `Amount` param: 14 | err = encoder.Encode(obj.Amount) 15 | if err != nil { 16 | return err 17 | } 18 | // Serialize `TargetAddr` param: 19 | err = encoder.Encode(obj.TargetAddr) 20 | if err != nil { 21 | return err 22 | } 23 | return nil 24 | } 25 | 26 | func (obj *TransferData) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 27 | // Deserialize `Amount`: 28 | err = decoder.Decode(&obj.Amount) 29 | if err != nil { 30 | return err 31 | } 32 | // Deserialize `TargetAddr`: 33 | err = decoder.Decode(&obj.TargetAddr) 34 | if err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /alert/common.go: -------------------------------------------------------------------------------- 1 | package alert 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Alerter interface { 10 | AlertText(msg string, err error) 11 | AlertTextLazy(msg string, err error) 12 | AlertTextLazyGroup(group string, msg string, err error) 13 | } 14 | 15 | type CommonAlerter struct { 16 | lazyInterval int64 17 | lazyReset int64 18 | lazyTimer map[string]int64 19 | mutex *sync.RWMutex 20 | } 21 | 22 | func NewCommonAlerter(lazyInterval int64, lazyReset int64) *CommonAlerter { 23 | return &CommonAlerter{ 24 | lazyInterval: lazyInterval, 25 | lazyReset: lazyReset, 26 | lazyTimer: make(map[string]int64), 27 | mutex: &sync.RWMutex{}, 28 | } 29 | } 30 | 31 | func (ca *CommonAlerter) AlertText(msg string, err error) { 32 | log.Printf("%s : %v", msg, err) 33 | } 34 | 35 | func (ca *CommonAlerter) AlertTextLazyGroup(group string, msg string, err error) { 36 | ca.DoAlertTextLazy(ca, group, msg, err) 37 | } 38 | 39 | func (ca *CommonAlerter) AlertTextLazy(msg string, err error) { 40 | ca.DoAlertTextLazy(ca, "", msg, err) 41 | } 42 | 43 | func (ca *CommonAlerter) DoAlertTextLazy(target Alerter, group string, msg string, err error) { 44 | ca.mutex.Lock() 45 | defer ca.mutex.Unlock() 46 | timer, ok := ca.lazyTimer[group] 47 | now := time.Now().Unix() 48 | if !ok { 49 | ca.lazyTimer[group] = now 50 | } else { 51 | if now-timer >= ca.lazyReset { 52 | ca.lazyTimer[group] = now 53 | return 54 | } 55 | if now-timer >= ca.lazyInterval { 56 | target.AlertText(msg, err) 57 | ca.lazyTimer[group] = now 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /alert/lark.go: -------------------------------------------------------------------------------- 1 | package alert 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | "github.com/owlto-finance/utils-go/network" 9 | ) 10 | 11 | // alerter := alert.NewLarkAlerter("https://open.larksuite.com/open-apis/bot/v2/hook/xxxx") 12 | 13 | type LarkAlerter struct { 14 | *CommonAlerter 15 | webhook string 16 | } 17 | 18 | func NewLarkAlerter(webhook string) *LarkAlerter { 19 | return &LarkAlerter{ 20 | webhook: strings.TrimSpace(webhook), 21 | CommonAlerter: NewCommonAlerter(120, 900), 22 | } 23 | } 24 | 25 | func (la *LarkAlerter) AlertTextLazyGroup(group string, msg string, err error) { 26 | la.DoAlertTextLazy(la, group, msg, err) 27 | } 28 | 29 | func (la *LarkAlerter) AlertTextLazy(msg string, err error) { 30 | la.DoAlertTextLazy(la, "", msg, err) 31 | } 32 | 33 | func (la *LarkAlerter) AlertText(msg string, err error) { 34 | data := map[string]interface{}{ 35 | "msg_type": "text", 36 | "content": map[string]interface{}{ 37 | "text": fmt.Sprintf("%s : %v", msg, err), 38 | }, 39 | } 40 | network.Request(la.webhook, data, nil) 41 | log.Println(msg, ":", err) 42 | } 43 | -------------------------------------------------------------------------------- /alert/larknotice.go: -------------------------------------------------------------------------------- 1 | package alert 2 | 3 | import ( 4 | "github.com/go-lark/lark" 5 | ) 6 | 7 | var LarkBot *Bot 8 | 9 | type Bot struct { 10 | *lark.Bot 11 | } 12 | 13 | func NewLarkBot(appID, appSecret string) (*Bot, error) { 14 | bot := lark.NewChatBot(appID, appSecret) 15 | err := bot.StartHeartbeat() 16 | if err != nil { 17 | return nil, err 18 | } 19 | larkBot := &Bot{ 20 | Bot: bot, 21 | } 22 | LarkBot = larkBot 23 | return larkBot, nil 24 | } 25 | -------------------------------------------------------------------------------- /apollosdk/apolloconfig.go: -------------------------------------------------------------------------------- 1 | package apollosdk 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/apolloconfig/agollo/v4" 8 | "github.com/apolloconfig/agollo/v4/env/config" 9 | ) 10 | 11 | type SDKConfig struct { 12 | AppID string 13 | Cluster string 14 | MetaAddr string 15 | Namespaces []string 16 | Secret string 17 | IsBackupConfig bool 18 | BackupConfigPath string 19 | } 20 | 21 | type ApolloSDK struct { 22 | client agollo.Client 23 | config SDKConfig 24 | } 25 | 26 | func NewApolloSDK(cfg SDKConfig) (*ApolloSDK, error) { 27 | clientConfig := &config.AppConfig{ 28 | AppID: cfg.AppID, 29 | Cluster: cfg.Cluster, 30 | IP: cfg.MetaAddr, 31 | IsBackupConfig: cfg.IsBackupConfig, 32 | BackupConfigPath: cfg.BackupConfigPath, 33 | Secret: cfg.Secret, 34 | } 35 | clientConfig.NamespaceName = strings.Join(cfg.Namespaces, config.Comma) 36 | 37 | client, err := agollo.StartWithConfig(func() (*config.AppConfig, error) { 38 | return clientConfig, nil 39 | }) 40 | if err != nil { 41 | return nil, fmt.Errorf("failed to start Apollo client: %w", err) 42 | } 43 | 44 | sdk := &ApolloSDK{ 45 | client: client, 46 | config: cfg, 47 | } 48 | 49 | return sdk, nil 50 | } 51 | 52 | func (sdk *ApolloSDK) GetString(namespace, key string) (string, error) { 53 | config := sdk.client.GetConfig(namespace) 54 | if config == nil { 55 | return "", fmt.Errorf("namespace %s not found", namespace) 56 | } 57 | return config.GetValue(key), nil 58 | } 59 | -------------------------------------------------------------------------------- /asynccache/README.md: -------------------------------------------------------------------------------- 1 | # asynccache 2 | 3 | ## Introduction 4 | 5 | `asynccache` fetches and updates the latest data periodically and supports expire a key if unused for a period. 6 | 7 | The functions it provides is listed below: 8 | ```go 9 | type AsyncCache interface { 10 | // SetDefault sets the default value of given key if it is new to the cache. 11 | // It is useful for cache warming up. 12 | // Param val should not be nil. 13 | SetDefault(key string, val interface{}) (exist bool) 14 | 15 | // Get tries to fetch a value corresponding to the given key from the cache. 16 | // If error occurs during the first time fetching, it will be cached until the 17 | // sequential fetching triggered by the refresh goroutine succeed. 18 | Get(key string) (val interface{}, err error) 19 | 20 | // GetOrSet tries to fetch a value corresponding to the given key from the cache. 21 | // If the key is not yet cached or error occurs, the default value will be set. 22 | GetOrSet(key string, defaultVal interface{}) (val interface{}) 23 | 24 | // Dump dumps all cache entries. 25 | // This will not cause expire to refresh. 26 | Dump() map[string]interface{} 27 | 28 | // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 29 | DeleteIf(shouldDelete func(key string) bool) 30 | 31 | // Close closes the async cache. 32 | // This should be called when the cache is no longer needed, or may lead to resource leak. 33 | Close() 34 | } 35 | ``` 36 | 37 | ## Example 38 | 39 | ```go 40 | var key, ret = "key", "ret" 41 | opt := Options{ 42 | RefreshDuration: time.Second, 43 | IsSame: func(key string, oldData, newData interface{}) bool { 44 | return false 45 | }, 46 | Fetcher: func(key string) (interface{}, error) { 47 | return ret, nil 48 | }, 49 | } 50 | c := NewAsyncCache(opt) 51 | 52 | v, err := c.Get(key) 53 | assert.NoError(err) 54 | assert.Equal(v.(string), ret) 55 | 56 | time.Sleep(time.Second / 2) 57 | ret = "change" 58 | v, err = c.Get(key) 59 | assert.NoError(err) 60 | assert.NotEqual(v.(string), ret) 61 | 62 | time.Sleep(time.Second) 63 | v, err = c.Get(key) 64 | assert.NoError(err) 65 | assert.Equal(v.(string), ret) 66 | ``` 67 | -------------------------------------------------------------------------------- /asynccache/asynccache.go: -------------------------------------------------------------------------------- 1 | package asynccache 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | sf "golang.org/x/sync/singleflight" 11 | ) 12 | 13 | // Options controls the behavior of AsyncCache. 14 | type Options struct { 15 | RefreshDuration time.Duration 16 | Fetcher func(key string) (interface{}, error) 17 | 18 | // If EnableExpire is true, ExpireDuration MUST be set. 19 | EnableExpire bool 20 | ExpireDuration time.Duration 21 | 22 | ErrorHandler func(key string, err error) 23 | ChangeHandler func(key string, oldData, newData interface{}) 24 | DeleteHandler func(key string, oldData interface{}) 25 | 26 | IsSame func(key string, oldData, newData interface{}) bool 27 | ErrLogFunc func(str string) 28 | } 29 | 30 | // AsyncCache . 31 | type AsyncCache interface { 32 | // SetDefault sets the default value of given key if it is new to the cache. 33 | // It is useful for cache warming up. 34 | // Param val should not be nil. 35 | SetDefault(key string, val interface{}) (exist bool) 36 | 37 | // Get tries to fetch a value corresponding to the given key from the cache. 38 | // If error occurs during the first time fetching, it will be cached until the 39 | // sequential fetching triggered by the refresh goroutine succeed. 40 | Get(key string) (val interface{}, err error) 41 | 42 | // GetOrSet tries to fetch a value corresponding to the given key from the cache. 43 | // If the key is not yet cached or error occurs, the default value will be set. 44 | GetOrSet(key string, defaultVal interface{}) (val interface{}) 45 | 46 | // Dump dumps all cache entries. 47 | // This will not cause expire to refresh. 48 | Dump() map[string]interface{} 49 | 50 | // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 51 | DeleteIf(shouldDelete func(key string) bool) 52 | 53 | // Close closes the async cache. 54 | // This should be called when the cache is no longer needed, or may lead to resource leak. 55 | Close() 56 | } 57 | 58 | // asyncCache . 59 | type asyncCache struct { 60 | sfg sf.Group 61 | opt Options 62 | data sync.Map 63 | } 64 | 65 | type tickerType int 66 | 67 | const ( 68 | refreshTicker tickerType = iota 69 | expireTicker 70 | ) 71 | 72 | type sharedTicker struct { 73 | sync.Mutex 74 | started bool 75 | stopChan chan bool 76 | ticker *time.Ticker 77 | caches map[*asyncCache]struct{} 78 | } 79 | 80 | var ( 81 | // 共用 ticker 82 | refreshTickerMap, expireTickerMap sync.Map 83 | ) 84 | 85 | type entry struct { 86 | val atomic.Value 87 | expire int32 // 0 means useful, 1 will expire 88 | err Error 89 | } 90 | 91 | func (e *entry) Store(x interface{}, err error) { 92 | if x != nil { 93 | e.val.Store(x) 94 | } else { 95 | e.val = atomic.Value{} 96 | } 97 | e.err.Store(err) 98 | } 99 | 100 | func (e *entry) Touch() { 101 | atomic.StoreInt32(&e.expire, 0) 102 | } 103 | 104 | // NewAsyncCache creates an AsyncCache. 105 | func NewAsyncCache(opt Options) AsyncCache { 106 | c := &asyncCache{ 107 | sfg: sf.Group{}, 108 | opt: opt, 109 | } 110 | if c.opt.ErrLogFunc == nil { 111 | c.opt.ErrLogFunc = func(str string) { 112 | log.Println(str) 113 | } 114 | } 115 | if c.opt.EnableExpire { 116 | if c.opt.ExpireDuration == 0 { 117 | panic("asynccache: invalid ExpireDuration") 118 | } 119 | ti, _ := expireTickerMap.LoadOrStore(c.opt.ExpireDuration, 120 | &sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)}) 121 | et := ti.(*sharedTicker) 122 | et.Lock() 123 | et.caches[c] = struct{}{} 124 | if !et.started { 125 | et.started = true 126 | et.ticker = time.NewTicker(c.opt.ExpireDuration) 127 | go et.tick(et.ticker, expireTicker) 128 | } 129 | et.Unlock() 130 | } 131 | 132 | ti, _ := refreshTickerMap.LoadOrStore(c.opt.RefreshDuration, 133 | &sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)}) 134 | rt := ti.(*sharedTicker) 135 | rt.Lock() 136 | rt.caches[c] = struct{}{} 137 | if !rt.started { 138 | rt.started = true 139 | rt.ticker = time.NewTicker(c.opt.RefreshDuration) 140 | go rt.tick(rt.ticker, refreshTicker) 141 | } 142 | rt.Unlock() 143 | return c 144 | } 145 | 146 | // SetDefault sets the default value of given key if it is new to the cache. 147 | func (c *asyncCache) SetDefault(key string, val interface{}) bool { 148 | ety := &entry{} 149 | ety.Store(val, nil) 150 | actual, exist := c.data.LoadOrStore(key, ety) 151 | if exist { 152 | actual.(*entry).Touch() 153 | } 154 | return exist 155 | } 156 | 157 | // Get tries to fetch a value corresponding to the given key from the cache. 158 | // If error occurs during in the first time fetching, it will be cached until the 159 | // sequential fetchings triggered by the refresh goroutine succeed. 160 | func (c *asyncCache) Get(key string) (val interface{}, err error) { 161 | var ok bool 162 | val, ok = c.data.Load(key) 163 | if ok { 164 | e := val.(*entry) 165 | e.Touch() 166 | return e.val.Load(), e.err.Load() 167 | } 168 | 169 | val, err, _ = c.sfg.Do(key, func() (v interface{}, e error) { 170 | v, e = c.opt.Fetcher(key) 171 | ety := &entry{} 172 | ety.Store(v, e) 173 | c.data.Store(key, ety) 174 | return 175 | }) 176 | return 177 | } 178 | 179 | // GetOrSet tries to fetch a value corresponding to the given key from the cache. 180 | // If the key is not yet cached or fetching failed, the default value will be set. 181 | func (c *asyncCache) GetOrSet(key string, def interface{}) (val interface{}) { 182 | if v, ok := c.data.Load(key); ok { 183 | e := v.(*entry) 184 | if e.err.Load() != nil { 185 | ety := &entry{} 186 | ety.Store(def, nil) 187 | c.data.Store(key, ety) 188 | return def 189 | } 190 | e.Touch() 191 | return e.val.Load() 192 | } 193 | 194 | val, _, _ = c.sfg.Do(key, func() (interface{}, error) { 195 | v, e := c.opt.Fetcher(key) 196 | if e != nil { 197 | v = def 198 | } 199 | ety := &entry{} 200 | ety.Store(v, nil) 201 | c.data.Store(key, ety) 202 | return v, nil 203 | }) 204 | return 205 | } 206 | 207 | // Dump dumps all cached entries. 208 | func (c *asyncCache) Dump() map[string]interface{} { 209 | data := make(map[string]interface{}) 210 | c.data.Range(func(key, val interface{}) bool { 211 | k, ok := key.(string) 212 | if !ok { 213 | c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 214 | c.data.Delete(key) 215 | return true 216 | } 217 | data[k] = val.(*entry).val.Load() 218 | return true 219 | }) 220 | return data 221 | } 222 | 223 | // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 224 | func (c *asyncCache) DeleteIf(shouldDelete func(key string) bool) { 225 | c.data.Range(func(key, value interface{}) bool { 226 | s := key.(string) 227 | if shouldDelete(s) { 228 | if c.opt.DeleteHandler != nil { 229 | go c.opt.DeleteHandler(s, value) 230 | } 231 | c.data.Delete(key) 232 | } 233 | return true 234 | }) 235 | } 236 | 237 | // Close stops the background goroutine. 238 | func (c *asyncCache) Close() { 239 | // close refresh ticker 240 | ti, _ := refreshTickerMap.Load(c.opt.RefreshDuration) 241 | rt := ti.(*sharedTicker) 242 | rt.Lock() 243 | delete(rt.caches, c) 244 | if len(rt.caches) == 0 { 245 | rt.stopChan <- true 246 | rt.started = false 247 | } 248 | rt.Unlock() 249 | 250 | if c.opt.EnableExpire { 251 | // close expire ticker 252 | ti, _ := expireTickerMap.Load(c.opt.ExpireDuration) 253 | et := ti.(*sharedTicker) 254 | et.Lock() 255 | delete(et.caches, c) 256 | if len(et.caches) == 0 { 257 | et.stopChan <- true 258 | et.started = false 259 | } 260 | et.Unlock() 261 | } 262 | } 263 | 264 | // tick . 265 | // pass ticker but not use t.ticker directly is to ignore race. 266 | func (t *sharedTicker) tick(ticker *time.Ticker, tt tickerType) { 267 | var wg sync.WaitGroup 268 | defer ticker.Stop() 269 | for { 270 | select { 271 | case <-ticker.C: 272 | t.Lock() 273 | for c := range t.caches { 274 | wg.Add(1) 275 | go func(c *asyncCache) { 276 | defer wg.Done() 277 | if tt == expireTicker { 278 | c.expire() 279 | } else { 280 | c.refresh() 281 | } 282 | }(c) 283 | } 284 | wg.Wait() 285 | t.Unlock() 286 | case stop := <-t.stopChan: 287 | if stop { 288 | return 289 | } 290 | } 291 | } 292 | } 293 | 294 | func (c *asyncCache) expire() { 295 | c.data.Range(func(key, value interface{}) bool { 296 | k, ok := key.(string) 297 | if !ok { 298 | c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 299 | c.data.Delete(key) 300 | return true 301 | } 302 | e, ok := value.(*entry) 303 | if !ok { 304 | c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value)) 305 | c.data.Delete(key) 306 | return true 307 | } 308 | if !atomic.CompareAndSwapInt32(&e.expire, 0, 1) { 309 | if c.opt.DeleteHandler != nil { 310 | go c.opt.DeleteHandler(k, value) 311 | } 312 | c.data.Delete(key) 313 | } 314 | 315 | return true 316 | }) 317 | } 318 | 319 | func (c *asyncCache) refresh() { 320 | c.data.Range(func(key, value interface{}) bool { 321 | k, ok := key.(string) 322 | if !ok { 323 | c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 324 | c.data.Delete(key) 325 | return true 326 | } 327 | e, ok := value.(*entry) 328 | if !ok { 329 | c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value)) 330 | c.data.Delete(key) 331 | return true 332 | } 333 | 334 | newVal, err := c.opt.Fetcher(k) 335 | if err != nil { 336 | if c.opt.ErrorHandler != nil { 337 | go c.opt.ErrorHandler(k, err) 338 | } 339 | if e.err.Load() != nil { 340 | e.err.Store(err) 341 | } 342 | return true 343 | } 344 | 345 | if c.opt.IsSame != nil && !c.opt.IsSame(k, e.val.Load(), newVal) { 346 | if c.opt.ChangeHandler != nil { 347 | go c.opt.ChangeHandler(k, e.val.Load(), newVal) 348 | } 349 | } 350 | 351 | e.Store(newVal, err) 352 | return true 353 | }) 354 | } 355 | -------------------------------------------------------------------------------- /asynccache/asynccache_test.go: -------------------------------------------------------------------------------- 1 | package asynccache 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetOK(t *testing.T) { 12 | var key, ret = "key", "ret" 13 | op := Options{ 14 | RefreshDuration: time.Second, 15 | IsSame: func(key string, oldData, newData interface{}) bool { 16 | return false 17 | }, 18 | Fetcher: func(key string) (interface{}, error) { 19 | return ret, nil 20 | }, 21 | } 22 | c := NewAsyncCache(op) 23 | 24 | v, err := c.Get(key) 25 | assert.NoError(t, err) 26 | assert.Equal(t, v.(string), ret) 27 | 28 | time.Sleep(time.Second / 2) 29 | ret = "change" 30 | v, err = c.Get(key) 31 | assert.NoError(t, err) 32 | assert.NotEqual(t, v.(string), ret) 33 | 34 | time.Sleep(time.Second) 35 | v, err = c.Get(key) 36 | assert.NoError(t, err) 37 | assert.Equal(t, v.(string), ret) 38 | } 39 | 40 | func TestGetErr(t *testing.T) { 41 | var key, ret = "key", "ret" 42 | var first = true 43 | op := Options{ 44 | RefreshDuration: time.Second + 100*time.Millisecond, 45 | IsSame: func(key string, oldData, newData interface{}) bool { 46 | return false 47 | }, 48 | Fetcher: func(key string) (interface{}, error) { 49 | if first { 50 | first = false 51 | return nil, errors.New("error") 52 | } 53 | return ret, nil 54 | }, 55 | } 56 | c := NewAsyncCache(op) 57 | 58 | v, err := c.Get(key) 59 | assert.Error(t, err) 60 | assert.Nil(t, v) 61 | 62 | time.Sleep(time.Second / 2) 63 | _, err2 := c.Get(key) 64 | assert.Equal(t, err, err2) 65 | 66 | time.Sleep(time.Second + 10*time.Millisecond) 67 | v, err = c.Get(key) 68 | assert.NoError(t, err) 69 | assert.Equal(t, v.(string), ret) 70 | } 71 | 72 | func TestGetOrSetOK(t *testing.T) { 73 | var key, ret, def = "key", "ret", "def" 74 | op := Options{ 75 | RefreshDuration: time.Second, 76 | IsSame: func(key string, oldData, newData interface{}) bool { 77 | return false 78 | }, 79 | Fetcher: func(key string) (interface{}, error) { 80 | return ret, nil 81 | }, 82 | } 83 | c := NewAsyncCache(op) 84 | 85 | v := c.GetOrSet(key, def) 86 | assert.Equal(t, v.(string), ret) 87 | 88 | time.Sleep(time.Second / 2) 89 | ret = "change" 90 | v = c.GetOrSet(key, def) 91 | assert.NotEqual(t, v.(string), ret) 92 | 93 | time.Sleep(time.Second) 94 | v = c.GetOrSet(key, def) 95 | assert.Equal(t, v.(string), ret) 96 | } 97 | 98 | func TestGetOrSetErr(t *testing.T) { 99 | var key, ret, def = "key", "ret", "def" 100 | var first = true 101 | op := Options{ 102 | RefreshDuration: time.Second + 500*time.Millisecond, 103 | IsSame: func(key string, oldData, newData interface{}) bool { 104 | return false 105 | }, 106 | Fetcher: func(key string) (interface{}, error) { 107 | if first { 108 | first = false 109 | return nil, errors.New("error") 110 | } 111 | return ret, nil 112 | }, 113 | } 114 | c := NewAsyncCache(op) 115 | 116 | v := c.GetOrSet(key, def) 117 | assert.Equal(t, v.(string), def) 118 | 119 | time.Sleep(time.Second / 2) 120 | v = c.GetOrSet(key, ret) 121 | assert.NotEqual(t, v.(string), ret) 122 | assert.Equal(t, v.(string), def) 123 | 124 | time.Sleep(time.Second + 500*time.Millisecond) 125 | v = c.GetOrSet(key, def) 126 | assert.Equal(t, v.(string), ret) 127 | } 128 | 129 | func TestSetDefault(t *testing.T) { 130 | op := Options{ 131 | RefreshDuration: time.Second, 132 | IsSame: func(key string, oldData, newData interface{}) bool { 133 | return false 134 | }, 135 | Fetcher: func(key string) (interface{}, error) { 136 | return nil, errors.New("error") 137 | }, 138 | } 139 | c := NewAsyncCache(op) 140 | 141 | v := c.GetOrSet("key1", "def1") 142 | assert.Equal(t, v.(string), "def1") 143 | 144 | exist := c.SetDefault("key2", "val2") 145 | assert.False(t, exist) 146 | v = c.GetOrSet("key2", "def2") 147 | assert.Equal(t, v.(string), "val2") 148 | 149 | exist = c.SetDefault("key2", "val3") 150 | assert.True(t, exist) 151 | v = c.GetOrSet("key2", "def2") 152 | assert.Equal(t, v.(string), "val2") 153 | } 154 | 155 | func TestDeleteIf(t *testing.T) { 156 | op := Options{ 157 | RefreshDuration: time.Second, 158 | IsSame: func(key string, oldData, newData interface{}) bool { 159 | return false 160 | }, 161 | Fetcher: func(key string) (interface{}, error) { 162 | return nil, errors.New("error") 163 | }, 164 | } 165 | c := NewAsyncCache(op) 166 | 167 | c.SetDefault("key", "val") 168 | v := c.GetOrSet("key", "def") 169 | assert.Equal(t, v.(string), "val") 170 | 171 | d, _ := c.(interface{ DeleteIf(func(key string) bool) }) 172 | d.DeleteIf(func(string) bool { return true }) 173 | 174 | v = c.GetOrSet("key", "def") 175 | assert.Equal(t, v.(string), "def") 176 | } 177 | 178 | func TestClose(t *testing.T) { 179 | var dur = time.Second / 10 180 | var cnt int 181 | op := Options{ 182 | RefreshDuration: dur - 10*time.Millisecond, 183 | IsSame: func(key string, oldData, newData interface{}) bool { 184 | return false 185 | }, 186 | Fetcher: func(key string) (interface{}, error) { 187 | cnt++ 188 | return cnt, nil 189 | }, 190 | EnableExpire: true, 191 | ExpireDuration: time.Second, 192 | } 193 | c := NewAsyncCache(op) 194 | 195 | v := c.GetOrSet("key", 10) 196 | assert.Equal(t, v.(int), 1) 197 | 198 | time.Sleep(dur) 199 | v = c.GetOrSet("key", 10) 200 | assert.Equal(t, v.(int), 2) 201 | 202 | time.Sleep(dur) 203 | v = c.GetOrSet("key", 10) 204 | assert.Equal(t, v.(int), 3) 205 | 206 | c.Close() 207 | 208 | time.Sleep(dur) 209 | v = c.GetOrSet("key", 10) 210 | assert.Equal(t, v.(int), 3) 211 | } 212 | 213 | func TestExpire(t *testing.T) { 214 | // trigger is used to mark whether fetcher is called 215 | trigger := false 216 | op := Options{ 217 | EnableExpire: true, 218 | ExpireDuration: 3 * time.Minute, 219 | RefreshDuration: time.Minute, 220 | IsSame: func(key string, oldData, newData interface{}) bool { 221 | return true 222 | }, 223 | Fetcher: func(key string) (interface{}, error) { 224 | trigger = true 225 | return "", nil 226 | }, 227 | } 228 | c := NewAsyncCache(op).(*asyncCache) 229 | 230 | // GetOrSet cannot trigger fetcher when SetDefault before 231 | c.SetDefault("key-default", "") 232 | c.SetDefault("key-alive", "") 233 | c.GetOrSet("key-alive", "") 234 | assert.False(t, trigger) 235 | 236 | c.Get("key-expire") 237 | assert.True(t, trigger) 238 | 239 | // first expire set tag 240 | c.expire() 241 | 242 | trigger = false 243 | c.Get("key-alive") 244 | assert.False(t, trigger) 245 | // second expire, both key-default & key-expire have been removed 246 | c.expire() 247 | c.refresh() // prove refresh does not affect expire 248 | 249 | trigger = false 250 | c.Get("key-alive") 251 | assert.False(t, trigger) 252 | trigger = false 253 | c.Get("key-default") 254 | assert.True(t, trigger) 255 | trigger = false 256 | c.Get("key-expire") 257 | assert.True(t, trigger) 258 | } 259 | 260 | func BenchmarkGet(b *testing.B) { 261 | var key = "key" 262 | op := Options{ 263 | RefreshDuration: time.Second, 264 | IsSame: func(key string, oldData, newData interface{}) bool { 265 | return false 266 | }, 267 | Fetcher: func(key string) (interface{}, error) { 268 | return "", nil 269 | }, 270 | } 271 | c := NewAsyncCache(op) 272 | 273 | b.ReportAllocs() 274 | b.ResetTimer() 275 | for i := 0; i < b.N; i++ { 276 | _, _ = c.Get(key) 277 | } 278 | } 279 | 280 | func BenchmarkGetParallel(b *testing.B) { 281 | var key = "key" 282 | op := Options{ 283 | RefreshDuration: time.Second, 284 | IsSame: func(key string, oldData, newData interface{}) bool { 285 | return false 286 | }, 287 | Fetcher: func(key string) (interface{}, error) { 288 | return "", nil 289 | }, 290 | } 291 | c := NewAsyncCache(op) 292 | 293 | b.ReportAllocs() 294 | b.ResetTimer() 295 | b.RunParallel(func(pb *testing.PB) { 296 | for pb.Next() { 297 | _, _ = c.Get(key) 298 | } 299 | }) 300 | } 301 | 302 | func BenchmarkGetOrSet(b *testing.B) { 303 | var key, def = "key", "def" 304 | op := Options{ 305 | RefreshDuration: time.Second, 306 | IsSame: func(key string, oldData, newData interface{}) bool { 307 | return false 308 | }, 309 | Fetcher: func(key string) (interface{}, error) { 310 | return "", nil 311 | }, 312 | } 313 | c := NewAsyncCache(op) 314 | 315 | b.ReportAllocs() 316 | b.ResetTimer() 317 | for i := 0; i < b.N; i++ { 318 | _ = c.GetOrSet(key, def) 319 | } 320 | } 321 | 322 | func BenchmarkGetOrSetParallel(b *testing.B) { 323 | var key, def = "key", "def" 324 | op := Options{ 325 | RefreshDuration: time.Second, 326 | IsSame: func(key string, oldData, newData interface{}) bool { 327 | return false 328 | }, 329 | Fetcher: func(key string) (interface{}, error) { 330 | return "", nil 331 | }, 332 | } 333 | c := NewAsyncCache(op) 334 | 335 | b.ReportAllocs() 336 | b.ResetTimer() 337 | b.RunParallel(func(pb *testing.PB) { 338 | for pb.Next() { 339 | _ = c.GetOrSet(key, def) 340 | } 341 | }) 342 | } 343 | 344 | func BenchmarkRefresh(b *testing.B) { 345 | var key, def = "key", "def" 346 | op := Options{ 347 | RefreshDuration: time.Second, 348 | IsSame: func(key string, oldData, newData interface{}) bool { 349 | return false 350 | }, 351 | Fetcher: func(key string) (interface{}, error) { 352 | return "", nil 353 | }, 354 | } 355 | c := NewAsyncCache(op).(*asyncCache) 356 | c.SetDefault(key, def) 357 | 358 | b.ReportAllocs() 359 | b.ResetTimer() 360 | for i := 0; i < b.N; i++ { 361 | c.refresh() 362 | } 363 | } 364 | 365 | func BenchmarkRefreshParallel(b *testing.B) { 366 | var key, def = "key", "def" 367 | op := Options{ 368 | RefreshDuration: time.Second, 369 | IsSame: func(key string, oldData, newData interface{}) bool { 370 | return false 371 | }, 372 | Fetcher: func(key string) (interface{}, error) { 373 | return "", nil 374 | }, 375 | } 376 | c := NewAsyncCache(op).(*asyncCache) 377 | c.SetDefault(key, def) 378 | 379 | b.ReportAllocs() 380 | b.ResetTimer() 381 | b.RunParallel(func(pb *testing.PB) { 382 | for pb.Next() { 383 | c.refresh() 384 | } 385 | }) 386 | } 387 | -------------------------------------------------------------------------------- /asynccache/atomic_error.go: -------------------------------------------------------------------------------- 1 | package asynccache 2 | 3 | import "sync/atomic" 4 | 5 | // Error is an atomic type-safe wrapper around Value for errors 6 | type Error struct{ v atomic.Value } 7 | 8 | // errorHolder is non-nil holder for error object. 9 | // atomic.Value panics on saving nil object, so err object needs to be 10 | // wrapped with valid object first. 11 | type errorHolder struct{ err error } 12 | 13 | // Load atomically loads the wrapped error 14 | func (e *Error) Load() error { 15 | v := e.v.Load() 16 | if v == nil { 17 | return nil 18 | } 19 | 20 | eh := v.(errorHolder) 21 | return eh.err 22 | } 23 | 24 | // Store atomically stores error. 25 | // NOTE: a holder object is allocated on each Store call. 26 | func (e *Error) Store(err error) { 27 | e.v.Store(errorHolder{err: err}) 28 | } 29 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/mitchellh/mapstructure" 10 | "github.com/owlto-finance/utils-go/system" 11 | "github.com/pelletier/go-toml" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | // import ( 16 | // "fmt" 17 | 18 | // "github.com/owlto-finance/utils-go/config" 19 | // ) 20 | 21 | // type Tel struct { 22 | // Num []int `mapstructure:"num"` // mapstructure tag should match the field name 23 | // } 24 | 25 | // type Conf struct { 26 | // DbBackend string `mapstructure:"db_backend"` 27 | // DbDsn string `mapstructure:"db_dsn"` 28 | // KmsUrl string `mapstructure:"kms_url"` 29 | // KmsApiKey string `mapstructure:"kms_api_key"` 30 | // ApiServerListen string `mapstructure:"api_server_listen"` 31 | // Telemetry Tel `mapstructure:"telemetry"` 32 | // IsTestnet bool `mapstructure:"is_testnet"` 33 | // } 34 | 35 | // func main() { 36 | // conf := Conf{ 37 | // DbBackend: "mysql", 38 | // DbDsn: "usbase", 39 | // KmsUrl: "ht.example.com", 40 | // KmsApiKey: "api_key", 41 | // ApiServerListen: ":9090", 42 | // Telemetry: Tel{Num: []int{3}}, 43 | // IsTestnet: true, 44 | // } 45 | 46 | // err := config.WriteConfigFile("/home/lzj/utils-go/a/b/c.toml", &conf) 47 | // if err != nil { 48 | // fmt.Println(err) 49 | // } 50 | // fmt.Println(conf) 51 | // } 52 | 53 | // WriteConfigFile renders config using the template and writes it to configFilePath. 54 | func WriteConfigFile(configFilePath string, config interface{}) error { 55 | configFilePath = strings.TrimSpace(configFilePath) 56 | fileType := strings.TrimPrefix(filepath.Ext(configFilePath), ".") 57 | 58 | configMap := make(map[string]interface{}) 59 | if err := mapstructure.Decode(config, &configMap); err != nil { 60 | return fmt.Errorf("error decoding struct to map: %w", err) 61 | 62 | } 63 | err := system.MakeDirAll(filepath.Dir(configFilePath)) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | var data []byte 69 | if fileType == "toml" { 70 | // Marshal the map to TOML format 71 | data, err = toml.Marshal(configMap) 72 | if err != nil { 73 | return fmt.Errorf("error marshaling map to TOML: %w", err) 74 | } 75 | } else { 76 | return fmt.Errorf("unsupport config file type: %s, only toml is support", fileType) 77 | } 78 | 79 | if err := os.WriteFile(configFilePath, data, 0o600); err != nil { 80 | return fmt.Errorf("failed to write file: %w", err) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func GetConfig(configFilePath string, defaultConfig interface{}) error { 87 | configFilePath = strings.TrimSpace(configFilePath) 88 | filename := filepath.Base(configFilePath) 89 | fileType := strings.TrimPrefix(filepath.Ext(configFilePath), ".") 90 | v := viper.New() 91 | v.SetConfigType(fileType) 92 | v.SetConfigName(filename) 93 | v.AddConfigPath(filepath.Dir(configFilePath)) 94 | 95 | if err := v.ReadInConfig(); err != nil { 96 | return fmt.Errorf("failed to read in %s: %w", configFilePath, err) 97 | } 98 | 99 | if err := v.Unmarshal(&defaultConfig); err != nil { 100 | return fmt.Errorf("error extracting app config: %w", err) 101 | } 102 | 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /contract/solana/contract.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::token::{self, Token, TokenAccount, Transfer as SplTransfer}; 3 | use solana_program::system_instruction; 4 | declare_id!("2SA6hTKWEGXVeY1ADVkiucGSw88cEoYDE6DKNozt73Si"); 5 | 6 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug)] 7 | pub struct TransferData { 8 | pub amount: u64, 9 | pub target_addr: String, 10 | } 11 | 12 | #[derive(Accounts)] 13 | pub struct TransferLamports<'info> { 14 | #[account(mut)] 15 | pub from: Signer<'info>, 16 | #[account(mut)] 17 | pub to: AccountInfo<'info>, 18 | pub system_program: Program<'info, System>, 19 | } 20 | 21 | #[derive(Accounts)] 22 | pub struct TransferSpl<'info> { 23 | pub from: Signer<'info>, 24 | #[account(mut)] 25 | pub from_ata: Account<'info, TokenAccount>, 26 | #[account(mut)] 27 | pub to_ata: Account<'info, TokenAccount>, 28 | pub token_program: Program<'info, Token>, 29 | } 30 | 31 | #[program] 32 | pub mod owlto_sol_transfer { 33 | use super::*; 34 | pub fn transfer_lamports( 35 | ctx: Context, 36 | transfer_data: TransferData, 37 | ) -> Result<()> { 38 | let from_account = &ctx.accounts.from; 39 | let to_account = &ctx.accounts.to; 40 | 41 | // Create the transfer instruction 42 | let transfer_instruction = 43 | system_instruction::transfer(from_account.key, to_account.key, transfer_data.amount); 44 | 45 | // Invoke the transfer instruction 46 | anchor_lang::solana_program::program::invoke_signed( 47 | &transfer_instruction, 48 | &[ 49 | from_account.to_account_info(), 50 | to_account.clone(), 51 | ctx.accounts.system_program.to_account_info(), 52 | ], 53 | &[], 54 | )?; 55 | 56 | Ok(()) 57 | } 58 | 59 | pub fn transfer_spl_tokens( 60 | ctx: Context, 61 | transfer_data: TransferData, 62 | ) -> Result<()> { 63 | let destination = &ctx.accounts.to_ata; 64 | let source = &ctx.accounts.from_ata; 65 | let token_program = &ctx.accounts.token_program; 66 | let authority = &ctx.accounts.from; 67 | 68 | // Transfer tokens from taker to initializer 69 | let cpi_accounts = SplTransfer { 70 | from: source.to_account_info().clone(), 71 | to: destination.to_account_info().clone(), 72 | authority: authority.to_account_info().clone(), 73 | }; 74 | let cpi_program = token_program.to_account_info(); 75 | 76 | token::transfer( 77 | CpiContext::new(cpi_program, cpi_accounts), 78 | transfer_data.amount, 79 | )?; 80 | Ok(()) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /contract/solana/test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createMint, 3 | createAssociatedTokenAccount, 4 | mintTo, 5 | TOKEN_PROGRAM_ID, 6 | } from "@solana/spl-token"; 7 | describe("Test transfers", () => { 8 | it("transferLamports", async () => { 9 | // Generate keypair for the new account 10 | const newAccountKp = new web3.Keypair(); 11 | // Send transaction 12 | 13 | interface TransferData { 14 | amount: BN; // Assuming 'amount' is a number 15 | targetAddr: string; // Assuming 'message' is a string 16 | } 17 | 18 | const transferData: TransferData = { 19 | amount: new BN(1000), // Assuming 'amount' is a BigNumber 20 | targetAddr: "Hello, world!", // Assuming 'message' is a string 21 | }; 22 | 23 | 24 | const txHash = await pg.program.methods 25 | .transferLamports(transferData) 26 | .accounts({ 27 | from: pg.wallet.publicKey, 28 | to: newAccountKp.publicKey, 29 | }) 30 | .signers([pg.wallet.keypair]) 31 | .rpc(); 32 | console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`); 33 | await pg.connection.confirmTransaction(txHash, "finalized"); 34 | const newAccountBalance = await pg.program.provider.connection.getBalance( 35 | newAccountKp.publicKey 36 | ); 37 | assert.strictEqual( 38 | newAccountBalance, 39 | transferData.amount.toNumber(), 40 | "The new account should have the transferred lamports" 41 | ); 42 | }); 43 | 44 | it("transferSplTokens", async () => { 45 | // Generate keypairs for the new accounts 46 | const fromKp = pg.wallet.keypair; 47 | const toKp = new web3.Keypair(); 48 | 49 | // Create a new mint and initialize it 50 | const mintKp = new web3.Keypair(); 51 | const mint = await createMint( 52 | pg.program.provider.connection, 53 | pg.wallet.keypair, 54 | fromKp.publicKey, 55 | null, 56 | 0 57 | ); 58 | 59 | // Create associated token accounts for the new accounts 60 | const fromAta = await createAssociatedTokenAccount( 61 | pg.program.provider.connection, 62 | pg.wallet.keypair, 63 | mint, 64 | fromKp.publicKey 65 | ); 66 | console.log("fromAta" , fromAta.toBase58()) 67 | const toAta = await createAssociatedTokenAccount( 68 | pg.program.provider.connection, 69 | pg.wallet.keypair, 70 | mint, 71 | toKp.publicKey 72 | ); 73 | console.log("toAta" , fromAta.toBase58()) 74 | // Mint tokens to the 'from' associated token account 75 | const mintAmount = 1000; 76 | await mintTo( 77 | pg.program.provider.connection, 78 | pg.wallet.keypair, 79 | mint, 80 | fromAta, 81 | pg.wallet.keypair.publicKey, 82 | mintAmount 83 | ); 84 | 85 | // Send transaction 86 | 87 | 88 | interface TransferData { 89 | amount: BN; // Assuming 'amount' is a number 90 | targetAddr: string; // Assuming 'message' is a string 91 | } 92 | 93 | const transferData: TransferData = { 94 | amount: new BN(999), // Assuming 'amount' is a BigNumber 95 | targetAddr: "Hello, world!", // Assuming 'message' is a string 96 | }; 97 | 98 | const txHash = await pg.program.methods 99 | .transferSplTokens(transferData) 100 | .accounts({ 101 | from: fromKp.publicKey, 102 | fromAta: fromAta, 103 | toAta: toAta, 104 | tokenProgram: TOKEN_PROGRAM_ID, 105 | }) 106 | .signers([pg.wallet.keypair, fromKp]) 107 | .rpc(); 108 | console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`); 109 | await pg.connection.confirmTransaction(txHash, "finalized"); 110 | const toTokenAccount = await pg.connection.getTokenAccountBalance(toAta); 111 | assert.strictEqual( 112 | toTokenAccount.value.uiAmount, 113 | transferData.amount.toNumber(), 114 | "The 'to' token account should have the transferred tokens" 115 | ); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /convert/convert.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func ConvertStringToInt32(value string) int32 { 13 | result, err := strconv.ParseInt(value, 10, 64) 14 | if err != nil { 15 | return 0 16 | } 17 | return int32(result) 18 | } 19 | 20 | func ConvertStringToInt(value string) int { 21 | result, err := strconv.ParseInt(value, 10, 64) 22 | if err != nil { 23 | return 0 24 | } 25 | return int(result) 26 | } 27 | 28 | func ConvertStringToInt64(value string) int64 { 29 | result, err := strconv.ParseInt(value, 10, 64) 30 | if err != nil { 31 | return 0 32 | } 33 | return result 34 | } 35 | 36 | func ConvertStringToUint64(value string) uint64 { 37 | result, err := strconv.ParseUint(value, 10, 64) 38 | if err != nil { 39 | return 0 40 | } 41 | return result 42 | } 43 | 44 | func ConvertStringToPtrTime(value string) *time.Time { 45 | t, err := time.Parse(time.RFC3339, value) 46 | if err != nil { 47 | return nil 48 | } 49 | return &t 50 | } 51 | 52 | func FormatDecimalString(inputStr string, decimalPlaces int) string { 53 | if inputStr == "" { 54 | return "0." + strings.Repeat("0", decimalPlaces) 55 | } 56 | 57 | inputStr = strings.Replace(inputStr, ".", "", 1) 58 | 59 | inputLen := len(inputStr) 60 | if inputLen <= decimalPlaces { 61 | return "0." + strings.Repeat("0", decimalPlaces-inputLen) + inputStr 62 | } 63 | 64 | pointPosition := inputLen - decimalPlaces 65 | integerPart := inputStr[:pointPosition] 66 | decimalPart := inputStr[pointPosition:] 67 | 68 | decimalPart = strings.TrimRight(decimalPart, "0") 69 | 70 | if decimalPart == "" { 71 | return integerPart 72 | } 73 | 74 | return integerPart + "." + decimalPart 75 | } 76 | 77 | // ConvertAndScale 通过缩放10的倍数,scale缩放倍数 78 | func ConvertAndScale(input string, scale int32) string { 79 | value, _, _ := new(big.Float).SetPrec(236).Parse(input, 10) 80 | return value.Quo(value, big.NewFloat(math.Pow10(int(scale)))).String() 81 | } 82 | 83 | func ConvertToJsonString(obj interface{}) string { 84 | jsonData, _ := json.Marshal(obj) 85 | return string(jsonData) 86 | } 87 | -------------------------------------------------------------------------------- /errors/error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/owlto-finance/utils-go/log" 8 | ) 9 | 10 | type BizError struct { 11 | Code int64 `json:"code"` 12 | Msg string `json:"msg"` 13 | Info map[string]interface{} `json:"info"` 14 | } 15 | 16 | func (e *BizError) Error() string { 17 | var sb strings.Builder 18 | sb.WriteString(fmt.Sprintf("code: %d, msg: %s", e.Code, e.Msg)) 19 | if len(e.Info) > 0 { 20 | sb.WriteString(" | info: {") 21 | infoParts := make([]string, 0, len(e.Info)) 22 | for key, value := range e.Info { 23 | infoParts = append(infoParts, fmt.Sprintf("%s = %v", key, value)) 24 | } 25 | sb.WriteString(strings.Join(infoParts, ", ")) 26 | sb.WriteString("}") 27 | } 28 | return sb.String() 29 | } 30 | 31 | func NewBizError(code int64, msg string) *BizError { 32 | return &BizError{Code: code, Msg: msg} 33 | } 34 | 35 | func (e *BizError) GetCode() int64 { 36 | return e.Code 37 | } 38 | 39 | func (e *BizError) GetMsg() string { 40 | return e.Msg 41 | } 42 | 43 | func (e *BizError) GetMsgPtr() *string { 44 | return &e.Msg 45 | } 46 | 47 | func (e *BizError) WithMsg(msg string) *BizError { 48 | e.Msg = msg 49 | return e 50 | } 51 | 52 | func (e *BizError) WithInfo(kv ...interface{}) *BizError { 53 | if len(kv)%2 != 0 { 54 | fmt.Println("Warning: WithInfo requires an even number of arguments, ignoring the last key-value pair.") 55 | kv = kv[:len(kv)-1] // Ignore the last key if it's unmatched 56 | } 57 | var info = make(map[string]interface{}) 58 | for k, v := range e.Info { 59 | info[k] = v 60 | } 61 | 62 | for i := 0; i < len(kv); i += 2 { 63 | key, ok := kv[i].(string) 64 | if !ok { 65 | log.Warn("Warning: WithInfo requires string keys, ignoring this key-value pair.") 66 | continue 67 | } 68 | info[key] = kv[i+1] 69 | } 70 | e.Info = info 71 | return e 72 | } 73 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/owlto-finance/utils-go 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/NethermindEth/juno v0.3.1 7 | github.com/NethermindEth/starknet.go v0.6.1 8 | github.com/blocto/solana-go-sdk v1.30.0 9 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 10 | github.com/ethereum/go-ethereum v1.13.14 11 | github.com/gagliardetto/binary v0.8.0 12 | github.com/gagliardetto/gofuzz v1.2.2 13 | github.com/gagliardetto/solana-go v1.10.0 14 | github.com/gagliardetto/treeout v0.1.4 15 | github.com/go-lark/lark v1.14.1 16 | github.com/hashicorp/go-metrics v0.5.3 17 | github.com/mitchellh/mapstructure v1.5.0 18 | github.com/pelletier/go-toml v1.9.5 19 | github.com/prometheus/client_golang v1.18.0 20 | github.com/prometheus/common v0.46.0 21 | github.com/sirupsen/logrus v1.9.0 22 | github.com/spf13/viper v1.18.2 23 | github.com/stretchr/testify v1.8.4 24 | golang.org/x/sync v0.5.0 25 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 26 | ) 27 | 28 | require ( 29 | filippo.io/edwards25519 v1.1.0 // indirect 30 | github.com/BurntSushi/toml v0.3.1 // indirect 31 | github.com/DataDog/datadog-go v3.2.0+incompatible // indirect 32 | github.com/Microsoft/go-winio v0.6.1 // indirect 33 | github.com/StackExchange/wmi v1.2.1 // indirect 34 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect 35 | github.com/apolloconfig/agollo/v4 v4.4.0 // indirect 36 | github.com/beorn7/perks v1.0.1 // indirect 37 | github.com/bits-and-blooms/bitset v1.10.0 // indirect 38 | github.com/blendle/zapdriver v1.3.1 // indirect 39 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 40 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 41 | github.com/consensys/bavard v0.1.13 // indirect 42 | github.com/consensys/gnark-crypto v0.12.1 // indirect 43 | github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect 44 | github.com/deckarep/golang-set/v2 v2.1.0 // indirect 45 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 46 | github.com/ethereum/c-kzg-4844 v0.4.0 // indirect 47 | github.com/fatih/color v1.14.1 // indirect 48 | github.com/fsnotify/fsnotify v1.7.0 // indirect 49 | github.com/fxamacker/cbor/v2 v2.4.0 // indirect 50 | github.com/go-ole/go-ole v1.3.0 // indirect 51 | github.com/google/uuid v1.6.0 // indirect 52 | github.com/gorilla/websocket v1.4.2 // indirect 53 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 54 | github.com/hashicorp/golang-lru v0.5.4 // indirect 55 | github.com/hashicorp/hcl v1.0.0 // indirect 56 | github.com/holiman/uint256 v1.2.4 // indirect 57 | github.com/json-iterator/go v1.1.12 // indirect 58 | github.com/klauspost/compress v1.17.0 // indirect 59 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 60 | github.com/magiconair/properties v1.8.7 // indirect 61 | github.com/mattn/go-colorable v0.1.13 // indirect 62 | github.com/mattn/go-isatty v0.0.17 // indirect 63 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 64 | github.com/mmcloughlin/addchain v0.4.0 // indirect 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 66 | github.com/modern-go/reflect2 v1.0.2 // indirect 67 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect 68 | github.com/mr-tron/base58 v1.2.0 // indirect 69 | github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 // indirect 70 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 71 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 72 | github.com/prometheus/client_model v0.5.0 // indirect 73 | github.com/prometheus/procfs v0.12.0 // indirect 74 | github.com/sagikazarmark/locafero v0.4.0 // indirect 75 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 76 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect 77 | github.com/sourcegraph/conc v0.3.0 // indirect 78 | github.com/spf13/afero v1.11.0 // indirect 79 | github.com/spf13/cast v1.6.0 // indirect 80 | github.com/spf13/pflag v1.0.5 // indirect 81 | github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect 82 | github.com/subosito/gotenv v1.6.0 // indirect 83 | github.com/supranational/blst v0.3.11 // indirect 84 | github.com/test-go/testify v1.1.4 // indirect 85 | github.com/tklauser/go-sysconf v0.3.12 // indirect 86 | github.com/tklauser/numcpus v0.6.1 // indirect 87 | github.com/x448/float16 v0.8.4 // indirect 88 | go.mongodb.org/mongo-driver v1.11.0 // indirect 89 | go.uber.org/atomic v1.10.0 // indirect 90 | go.uber.org/multierr v1.9.0 // indirect 91 | go.uber.org/ratelimit v0.2.0 // indirect 92 | go.uber.org/zap v1.24.0 // indirect 93 | golang.org/x/crypto v0.17.0 // indirect 94 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 95 | golang.org/x/mod v0.14.0 // indirect 96 | golang.org/x/sys v0.16.0 // indirect 97 | golang.org/x/term v0.15.0 // indirect 98 | golang.org/x/text v0.14.0 // indirect 99 | golang.org/x/time v0.5.0 // indirect 100 | golang.org/x/tools v0.15.0 // indirect 101 | google.golang.org/protobuf v1.32.0 // indirect 102 | gopkg.in/ini.v1 v1.67.0 // indirect 103 | gopkg.in/yaml.v3 v3.0.1 // indirect 104 | rsc.io/tmplfunc v0.0.3 // indirect 105 | ) 106 | -------------------------------------------------------------------------------- /json_extract/json_extract.go: -------------------------------------------------------------------------------- 1 | package json_extract 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func ExtractValueFromJSON(jsonData string, path string) (interface{}, error) { 12 | var data interface{} 13 | if err := json.Unmarshal([]byte(jsonData), &data); err != nil { 14 | return nil, err 15 | } 16 | 17 | // 分割路径 "estimation.costsDetails[0].chain" -> ["estimation", "costsDetails[0]", "chain"] 18 | pathParts := strings.Split(path, ".") 19 | 20 | current := data 21 | for _, part := range pathParts { 22 | if strings.Contains(part, "[") { 23 | // 处理数组部分 24 | re := regexp.MustCompile(`(\w+)\[(\d+)\]`) 25 | matches := re.FindStringSubmatch(part) 26 | if len(matches) != 3 { 27 | return nil, fmt.Errorf("invalid path segment: %s", part) 28 | } 29 | 30 | key := matches[1] 31 | index, _ := strconv.Atoi(matches[2]) 32 | 33 | // 转到指定的key 34 | if mapped, ok := current.(map[string]interface{}); ok { 35 | if value, found := mapped[key]; found { 36 | // 转到数组的指定索引 37 | if sliced, ok := value.([]interface{}); ok { 38 | if index < len(sliced) { 39 | current = sliced[index] 40 | } else { 41 | return nil, fmt.Errorf("index out of range: %d", index) 42 | } 43 | } else { 44 | return nil, fmt.Errorf("not an array: %s", key) 45 | } 46 | } else { 47 | return nil, fmt.Errorf("key not found: %s", key) 48 | } 49 | } else { 50 | return nil, fmt.Errorf("not a map before key: %s", key) 51 | } 52 | } else { 53 | if mapped, ok := current.(map[string]interface{}); ok { 54 | if value, found := mapped[part]; found { 55 | current = value 56 | } else { 57 | return nil, fmt.Errorf("key not found: %s", part) 58 | } 59 | } else { 60 | return nil, fmt.Errorf("not a map at key: %s", part) 61 | } 62 | } 63 | } 64 | return current, nil 65 | } 66 | 67 | func ExtractStringValueFromJSON(jsonData string, path string) (string, error) { 68 | value, err := ExtractValueFromJSON(jsonData, path) 69 | if err != nil { 70 | return "", err 71 | } 72 | return fmt.Sprintf("%v", value), nil 73 | } 74 | 75 | func ExtractStringValueFromObj(obj interface{}, path string) (string, error) { 76 | jsonData, _ := json.Marshal(obj) 77 | value, err := ExtractValueFromJSON(string(jsonData), path) 78 | if err != nil { 79 | return "", err 80 | } 81 | return fmt.Sprintf("%v", value), nil 82 | } 83 | 84 | func ExtractInt64ValueFromJSON(jsonData string, path string) (int64, error) { 85 | value, err := ExtractValueFromJSON(jsonData, path) 86 | if err != nil { 87 | return 0, err 88 | } 89 | 90 | if floatValue, ok := value.(float64); ok { 91 | return int64(floatValue), nil 92 | } else { 93 | return 0, fmt.Errorf("not an integer: %s", path) 94 | } 95 | } 96 | 97 | func ExtractInt64ValueFromObj(obj interface{}, path string) (int64, error) { 98 | jsonData, _ := json.Marshal(obj) 99 | value, err := ExtractValueFromJSON(string(jsonData), path) 100 | if err != nil { 101 | return 0, err 102 | } 103 | 104 | if floatValue, ok := value.(float64); ok { 105 | return int64(floatValue), nil 106 | } else { 107 | return 0, fmt.Errorf("not an integer: %s", path) 108 | } 109 | } 110 | 111 | func ExtractInt32ValueFromJSON(jsonData string, path string) (int32, error) { 112 | value, err := ExtractValueFromJSON(jsonData, path) 113 | if err != nil { 114 | return 0, err 115 | } 116 | 117 | if floatValue, ok := value.(float64); ok { 118 | return int32(floatValue), nil 119 | } else { 120 | return 0, fmt.Errorf("not an integer: %s", path) 121 | } 122 | } 123 | 124 | func ExtractInt32ValueFromObj(obj interface{}, path string) (int32, error) { 125 | jsonData, _ := json.Marshal(obj) 126 | value, err := ExtractValueFromJSON(string(jsonData), path) 127 | if err != nil { 128 | return 0, err 129 | } 130 | 131 | if floatValue, ok := value.(float64); ok { 132 | return int32(floatValue), nil 133 | } else { 134 | return 0, fmt.Errorf("not an integer: %s", path) 135 | } 136 | } 137 | 138 | func ExtractSliceValueFromJSON(jsonData string, path string) ([]interface{}, error) { 139 | value, err := ExtractValueFromJSON(jsonData, path) 140 | if err != nil { 141 | return nil, err 142 | } 143 | if slice, ok := value.([]interface{}); ok { 144 | return slice, nil 145 | } else { 146 | return nil, fmt.Errorf("not an array: %s", path) 147 | } 148 | } 149 | 150 | func ExtractSliceValueFromObj(obj interface{}, path string) ([]interface{}, error) { 151 | jsonData, _ := json.Marshal(obj) 152 | value, err := ExtractValueFromJSON(string(jsonData), path) 153 | if err != nil { 154 | return nil, err 155 | } 156 | if slice, ok := value.([]interface{}); ok { 157 | return slice, nil 158 | } else { 159 | return nil, fmt.Errorf("not an array: %s", path) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /loader/account.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type Account struct { 13 | Id int64 14 | ChainInfoId int64 15 | Address string 16 | } 17 | 18 | type AccountManager struct { 19 | idAccounts map[int64]*Account 20 | addressCidAccounts map[string]map[int64]*Account 21 | cidAddressAccounts map[int64]map[string]*Account 22 | db *sql.DB 23 | alerter alert.Alerter 24 | mutex *sync.RWMutex 25 | } 26 | 27 | func NewAccountManager(db *sql.DB, alerter alert.Alerter) *AccountManager { 28 | return &AccountManager{ 29 | idAccounts: make(map[int64]*Account), 30 | addressCidAccounts: make(map[string]map[int64]*Account), 31 | cidAddressAccounts: make(map[int64]map[string]*Account), 32 | db: db, 33 | alerter: alerter, 34 | mutex: &sync.RWMutex{}, 35 | } 36 | } 37 | 38 | func (mgr *AccountManager) GetAccountById(id int64) (*Account, bool) { 39 | mgr.mutex.RLock() 40 | acc, ok := mgr.idAccounts[id] 41 | mgr.mutex.RUnlock() 42 | return acc, ok 43 | } 44 | 45 | func (mgr *AccountManager) HasAddress(address string) bool { 46 | mgr.mutex.RLock() 47 | _, ok := mgr.addressCidAccounts[strings.ToLower(strings.TrimSpace(address))] 48 | mgr.mutex.RUnlock() 49 | return ok 50 | } 51 | 52 | func (mgr *AccountManager) GetAddresses(cid int64) []string { 53 | addrs := make([]string, 0) 54 | mgr.mutex.RLock() 55 | accs, ok := mgr.cidAddressAccounts[cid] 56 | if ok { 57 | for _, acc := range accs { 58 | addrs = append(addrs, acc.Address) 59 | } 60 | } 61 | mgr.mutex.RUnlock() 62 | return addrs 63 | } 64 | 65 | func (mgr *AccountManager) GetAccountByAddressCid(address string, cid int64) (*Account, bool) { 66 | mgr.mutex.RLock() 67 | defer mgr.mutex.RUnlock() 68 | accs, ok := mgr.addressCidAccounts[strings.ToLower(strings.TrimSpace(address))] 69 | if ok { 70 | acc, ok := accs[cid] 71 | return acc, ok 72 | } 73 | return nil, false 74 | } 75 | 76 | func (mgr *AccountManager) LoadAllAccounts() { 77 | // Query the database to select only id and name fields 78 | rows, err := mgr.db.Query("SELECT id, chain_id, address FROM t_account") 79 | 80 | if err != nil || rows == nil { 81 | mgr.alerter.AlertText("select t_account error", err) 82 | return 83 | } 84 | 85 | defer rows.Close() 86 | 87 | idAccounts := make(map[int64]*Account) 88 | addressCidAccounts := make(map[string]map[int64]*Account) 89 | cidAddressAccounts := make(map[int64]map[string]*Account) 90 | counter := 0 91 | 92 | // Iterate over the result set 93 | for rows.Next() { 94 | var acc Account 95 | if err := rows.Scan(&acc.Id, &acc.ChainInfoId, &acc.Address); err != nil { 96 | mgr.alerter.AlertText("scan t_account row error", err) 97 | } else { 98 | acc.Address = strings.TrimSpace(acc.Address) 99 | 100 | idAccounts[acc.Id] = &acc 101 | lowerAddr := strings.ToLower(acc.Address) 102 | 103 | accs, ok := addressCidAccounts[lowerAddr] 104 | if !ok { 105 | accs = make(map[int64]*Account) 106 | addressCidAccounts[lowerAddr] = accs 107 | } 108 | accs[acc.ChainInfoId] = &acc 109 | 110 | addraccs, ok := cidAddressAccounts[acc.ChainInfoId] 111 | if !ok { 112 | addraccs = make(map[string]*Account) 113 | cidAddressAccounts[acc.ChainInfoId] = addraccs 114 | } 115 | addraccs[lowerAddr] = &acc 116 | 117 | counter++ 118 | } 119 | } 120 | 121 | // Check for errors from iterating over rows 122 | if err := rows.Err(); err != nil { 123 | mgr.alerter.AlertText("get next t_account row error", err) 124 | return 125 | } 126 | 127 | mgr.mutex.Lock() 128 | mgr.idAccounts = idAccounts 129 | mgr.addressCidAccounts = addressCidAccounts 130 | mgr.cidAddressAccounts = cidAddressAccounts 131 | mgr.mutex.Unlock() 132 | log.Println("load all account: ", counter) 133 | 134 | } 135 | -------------------------------------------------------------------------------- /loader/bridge_fee.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/owlto-finance/utils-go/alert" 12 | "github.com/owlto-finance/utils-go/util" 13 | ) 14 | 15 | type BridgeFee struct { 16 | TokenName string 17 | FromChainName string 18 | ToChainName string 19 | BridgeFeeRatioLv1 int64 20 | BridgeFeeRatioLv2 int64 21 | BridgeFeeRatioLv3 int64 22 | BridgeFeeRatioLv4 int64 23 | AmountLv1 float64 24 | AmountLv2 float64 25 | AmountLv3 float64 26 | AmountLv4 float64 27 | KeepDecimal int32 28 | 29 | AmountLv1Str string 30 | AmountLv2Str string 31 | AmountLv3Str string 32 | AmountLv4Str string 33 | } 34 | 35 | type BridgeFeeManager struct { 36 | tokenFromToBridgeFees map[string]map[string]map[string]*BridgeFee 37 | 38 | db *sql.DB 39 | alerter alert.Alerter 40 | mutex *sync.RWMutex 41 | } 42 | 43 | func NewBridgeFeeManager(db *sql.DB, alerter alert.Alerter) *BridgeFeeManager { 44 | return &BridgeFeeManager{ 45 | tokenFromToBridgeFees: make(map[string]map[string]map[string]*BridgeFee), 46 | 47 | db: db, 48 | alerter: alerter, 49 | mutex: &sync.RWMutex{}, 50 | } 51 | } 52 | 53 | func (mgr *BridgeFeeManager) GetBridgeFee(token string, from string, to string) (*BridgeFee, bool) { 54 | mgr.mutex.RLock() 55 | defer mgr.mutex.RUnlock() 56 | ftInfos, ok := mgr.tokenFromToBridgeFees[strings.ToLower(strings.TrimSpace(token))] 57 | if ok { 58 | infos, ok := ftInfos[strings.ToLower(strings.TrimSpace(from))] 59 | if ok { 60 | info, ok := infos[strings.ToLower(strings.TrimSpace(to))] 61 | return info, ok 62 | } 63 | 64 | } 65 | return nil, false 66 | } 67 | 68 | func (mgr *BridgeFeeManager) LoadAllBridgeFee(tokenInfoMgr TokenInfoManager) { 69 | // Query the database to select only id and name fields 70 | rows, err := mgr.db.Query("SELECT token_name, from_chain, to_chain, bridge_fee_ratio_lv1, bridge_fee_ratio_lv2, bridge_fee_ratio_lv3, bridge_fee_ratio_lv4, amount_lv1, amount_lv2, amount_lv3, amount_lv4 FROM t_dynamic_bridge_fee") 71 | 72 | if err != nil || rows == nil { 73 | mgr.alerter.AlertText("select t_dynamic_bridge_fee error", err) 74 | return 75 | } 76 | 77 | kdrows, kderr := mgr.db.Query("SELECT token, keep_decimal FROM t_bridge_fee_decimal") 78 | if kderr != nil { 79 | mgr.alerter.AlertText("select t_bridge_fee_decimal error", kderr) 80 | } 81 | 82 | defer rows.Close() 83 | defer kdrows.Close() 84 | 85 | tokenDecimal := make(map[string]int64) 86 | for kdrows.Next() { 87 | var tokenName string 88 | var keepDecimal int64 89 | if err := kdrows.Scan(&tokenName, &keepDecimal); err != nil { 90 | mgr.alerter.AlertText("scan t_bridge_fee_decimal row error", err) 91 | } else { 92 | tokenName = strings.TrimSpace(tokenName) 93 | tokenDecimal[strings.ToLower(tokenName)] = keepDecimal 94 | } 95 | } 96 | 97 | tokenFromToBridgeFees := make(map[string]map[string]map[string]*BridgeFee) 98 | counter := 0 99 | 100 | // Iterate over the result set 101 | for rows.Next() { 102 | var bridgeFee BridgeFee 103 | 104 | if err := rows.Scan(&bridgeFee.TokenName, &bridgeFee.FromChainName, &bridgeFee.ToChainName, &bridgeFee.BridgeFeeRatioLv1, &bridgeFee.BridgeFeeRatioLv2, &bridgeFee.BridgeFeeRatioLv3, &bridgeFee.BridgeFeeRatioLv4, &bridgeFee.AmountLv1Str, &bridgeFee.AmountLv2Str, &bridgeFee.AmountLv3Str, &bridgeFee.AmountLv4Str); err != nil { 105 | mgr.alerter.AlertText("scan t_dynamic_bridge_fee row error", err) 106 | } else { 107 | bridgeFee.FromChainName = strings.TrimSpace(bridgeFee.FromChainName) 108 | bridgeFee.ToChainName = strings.TrimSpace(bridgeFee.ToChainName) 109 | bridgeFee.TokenName = strings.TrimSpace(bridgeFee.TokenName) 110 | 111 | amount1, err := strconv.ParseFloat(bridgeFee.AmountLv1Str, 64) 112 | if err != nil { 113 | mgr.alerter.AlertText("t_dynamic_bridge_fee amount1 not float", err) 114 | continue 115 | } 116 | amount2, err := strconv.ParseFloat(bridgeFee.AmountLv2Str, 64) 117 | if err != nil { 118 | mgr.alerter.AlertText("t_dynamic_bridge_fee amount2 not float", err) 119 | continue 120 | } 121 | amount3, err := strconv.ParseFloat(bridgeFee.AmountLv3Str, 64) 122 | if err != nil { 123 | mgr.alerter.AlertText("t_dynamic_bridge_fee amount3 not float", err) 124 | continue 125 | } 126 | amount4, err := strconv.ParseFloat(bridgeFee.AmountLv4Str, 64) 127 | if err != nil { 128 | mgr.alerter.AlertText("t_dynamic_bridge_fee amount4 not float", err) 129 | continue 130 | } 131 | 132 | bridgeFee.AmountLv1 = amount1 133 | bridgeFee.AmountLv2 = amount2 134 | bridgeFee.AmountLv3 = amount3 135 | bridgeFee.AmountLv4 = amount4 136 | 137 | tokenInfo, ok := tokenInfoMgr.GetByChainNameTokenName(strings.ToLower(bridgeFee.FromChainName), strings.ToLower(bridgeFee.TokenName)) 138 | dbKeepDecimal, kdexist := tokenDecimal[strings.ToLower(bridgeFee.TokenName)] 139 | if !ok && !kdexist { 140 | mgr.alerter.AlertText("t_dynamic_bridge_fee keep decimal not found: token "+bridgeFee.TokenName+" chain "+bridgeFee.FromChainName, err) 141 | continue 142 | } else if kdexist { 143 | bridgeFee.KeepDecimal = int32(dbKeepDecimal) 144 | } else if ok { 145 | bridgeFee.KeepDecimal = int32(tokenInfo.Decimals) 146 | } 147 | 148 | ftInfos, ok := tokenFromToBridgeFees[strings.ToLower(bridgeFee.TokenName)] 149 | if !ok { 150 | ftInfos = make(map[string]map[string]*BridgeFee) 151 | tokenFromToBridgeFees[strings.ToLower(bridgeFee.TokenName)] = ftInfos 152 | } 153 | infos, ok := ftInfos[strings.ToLower(bridgeFee.FromChainName)] 154 | if !ok { 155 | infos = make(map[string]*BridgeFee) 156 | ftInfos[strings.ToLower(bridgeFee.FromChainName)] = infos 157 | } 158 | infos[strings.ToLower(bridgeFee.ToChainName)] = &bridgeFee 159 | 160 | counter++ 161 | } 162 | } 163 | 164 | // Check for errors from iterating over rows 165 | if err := rows.Err(); err != nil { 166 | mgr.alerter.AlertText("get next t_dynamic_bridge_fee row error", err) 167 | return 168 | } 169 | 170 | mgr.mutex.Lock() 171 | mgr.tokenFromToBridgeFees = tokenFromToBridgeFees 172 | mgr.mutex.Unlock() 173 | log.Println("load all bridge fee: ", counter) 174 | 175 | } 176 | 177 | func (mgr *BridgeFeeManager) FromUiString(amount *big.Int, bridgeFee string, decimal int32, keepDecimal int32) *big.Int { 178 | value := big.NewInt(0) 179 | value.Add(value, amount) 180 | 181 | if bridgeFee != "" { 182 | bridgeFeeValue, err := util.FromUiString(bridgeFee, decimal) 183 | if err == nil { 184 | bridgeFeeAmount := new(big.Int) 185 | bridgeFeeAmount.Mul(value, bridgeFeeValue) 186 | bridgeFeeAmount.Div(value, big.NewInt(100000000)) 187 | 188 | scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimal-keepDecimal)), nil) 189 | modAmount := new(big.Int) 190 | modAmount.Mod(bridgeFeeAmount, scale) 191 | bridgeFeeAmount.Sub(bridgeFeeAmount, modAmount) 192 | 193 | value.Sub(value, bridgeFeeAmount) 194 | } 195 | } 196 | 197 | return value 198 | } 199 | 200 | func (mgr *BridgeFeeManager) GetIncludedBridgeFeeBigInt(tokenName string, fromChainName string, toChainName string, value *big.Int, decimal int32) (int64, bool) { 201 | bridgeFee, ok := mgr.GetBridgeFee(tokenName, fromChainName, toChainName) 202 | if !ok { 203 | return 0, false 204 | } 205 | 206 | keepDecimal := decimal 207 | if bridgeFee.KeepDecimal < decimal { 208 | keepDecimal = bridgeFee.KeepDecimal 209 | } 210 | 211 | AmountLv1BigInt, err := util.FromUiString(bridgeFee.AmountLv1Str, decimal) 212 | if err != nil { 213 | return 0, false 214 | } 215 | AmountLv2BigInt, err := util.FromUiString(bridgeFee.AmountLv2Str, decimal) 216 | if err != nil { 217 | return 0, false 218 | } 219 | AmountLv3BigInt, err := util.FromUiString(bridgeFee.AmountLv3Str, decimal) 220 | if err != nil { 221 | return 0, false 222 | } 223 | 224 | if AmountLv1BigInt.Cmp(mgr.FromUiString(value, strconv.FormatInt(bridgeFee.BridgeFeeRatioLv1, 10), decimal, keepDecimal)) > 0 { 225 | return bridgeFee.BridgeFeeRatioLv1, true 226 | } else if AmountLv2BigInt.Cmp(mgr.FromUiString(value, strconv.FormatInt(bridgeFee.BridgeFeeRatioLv2, 10), decimal, keepDecimal)) > 0 { 227 | return bridgeFee.BridgeFeeRatioLv2, true 228 | } else if AmountLv3BigInt.Cmp(mgr.FromUiString(value, strconv.FormatInt(bridgeFee.BridgeFeeRatioLv3, 10), decimal, keepDecimal)) > 0 { 229 | return bridgeFee.BridgeFeeRatioLv3, true 230 | } else { 231 | return bridgeFee.BridgeFeeRatioLv4, true 232 | } 233 | 234 | } 235 | 236 | func (mgr *BridgeFeeManager) GetBridgeFeeNotIncluded(tokenName string, fromChainName string, toChainName string, value float64) (int64, bool) { 237 | bridgeFee, ok := mgr.GetBridgeFee(tokenName, fromChainName, toChainName) 238 | if !ok { 239 | return 0, false 240 | } 241 | 242 | if value < bridgeFee.AmountLv1 { 243 | return bridgeFee.BridgeFeeRatioLv1, true 244 | } else if value < bridgeFee.AmountLv2 { 245 | return bridgeFee.BridgeFeeRatioLv2, true 246 | } else if value < bridgeFee.AmountLv3 { 247 | return bridgeFee.BridgeFeeRatioLv3, true 248 | } else { 249 | return bridgeFee.BridgeFeeRatioLv4, true 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /loader/chain_info.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/NethermindEth/starknet.go/rpc" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | ethrpc "github.com/ethereum/go-ethereum/rpc" 13 | solrpc "github.com/gagliardetto/solana-go/rpc" 14 | "github.com/owlto-finance/utils-go/alert" 15 | ) 16 | 17 | type Backend int32 18 | 19 | const ( 20 | EthereumBackend Backend = iota + 1 21 | StarknetBackend 22 | SolanaBackend 23 | BitcoinBackend 24 | ZksliteBackend 25 | TonBackend 26 | CosmosBackend 27 | ) 28 | 29 | type ChainInfo struct { 30 | Id int64 31 | ChainId string 32 | RealChainId string 33 | Name string 34 | AliasName string 35 | Backend Backend 36 | Eip1559 int8 37 | NetworkCode int32 38 | Icon string 39 | BlockInterval int32 40 | RpcEndPoint string 41 | ExplorerUrl string 42 | OfficialRpc string 43 | Disabled int8 44 | IsTestnet int8 45 | OrderWeight int32 46 | GasTokenName string 47 | GasTokenDecimal int32 48 | TransferContractAddress sql.NullString 49 | DepositContractAddress sql.NullString 50 | Layer1 sql.NullString 51 | Client interface{} 52 | } 53 | 54 | func (ci *ChainInfo) GetInt32ChainId() int32 { 55 | chainid, _ := strconv.ParseInt(ci.ChainId, 10, 32) 56 | return int32(chainid) 57 | } 58 | 59 | func (ci *ChainInfo) GetInt64ChainId() int64 { 60 | chainid, _ := strconv.ParseInt(ci.ChainId, 10, 64) 61 | return chainid 62 | } 63 | 64 | type ChainInfoManager struct { 65 | idChains map[int64]*ChainInfo 66 | chainIdChains map[string]*ChainInfo 67 | nameChains map[string]*ChainInfo 68 | netcodeChains map[int32]*ChainInfo 69 | 70 | db *sql.DB 71 | alerter alert.Alerter 72 | mutex *sync.RWMutex 73 | } 74 | 75 | func NewChainInfoManager(db *sql.DB, alerter alert.Alerter) *ChainInfoManager { 76 | return &ChainInfoManager{ 77 | idChains: make(map[int64]*ChainInfo), 78 | chainIdChains: make(map[string]*ChainInfo), 79 | nameChains: make(map[string]*ChainInfo), 80 | netcodeChains: make(map[int32]*ChainInfo), 81 | db: db, 82 | alerter: alerter, 83 | mutex: &sync.RWMutex{}, 84 | } 85 | } 86 | 87 | func (mgr *ChainInfoManager) GetChainInfoIds() []int64 { 88 | mgr.mutex.RLock() 89 | ids := make([]int64, 0, len(mgr.idChains)) 90 | 91 | for id := range mgr.idChains { 92 | ids = append(ids, id) 93 | } 94 | mgr.mutex.RUnlock() 95 | return ids 96 | } 97 | 98 | func (mgr *ChainInfoManager) GetChainInfoById(id int64) (*ChainInfo, bool) { 99 | mgr.mutex.RLock() 100 | chain, ok := mgr.idChains[id] 101 | mgr.mutex.RUnlock() 102 | return chain, ok 103 | } 104 | func (mgr *ChainInfoManager) GetChainInfoByInt32ChainId(chainId int32) (*ChainInfo, bool) { 105 | return mgr.GetChainInfoByChainId(strconv.FormatInt(int64(chainId), 10)) 106 | } 107 | func (mgr *ChainInfoManager) GetChainInfoByInt64ChainId(chainId int64) (*ChainInfo, bool) { 108 | return mgr.GetChainInfoByChainId(strconv.FormatInt(chainId, 10)) 109 | } 110 | func (mgr *ChainInfoManager) GetChainInfoByChainId(chainId string) (*ChainInfo, bool) { 111 | mgr.mutex.RLock() 112 | chain, ok := mgr.chainIdChains[strings.ToLower(strings.TrimSpace(chainId))] 113 | mgr.mutex.RUnlock() 114 | return chain, ok 115 | } 116 | func (mgr *ChainInfoManager) GetChainInfoByName(name string) (*ChainInfo, bool) { 117 | mgr.mutex.RLock() 118 | chain, ok := mgr.nameChains[strings.ToLower(strings.TrimSpace(name))] 119 | mgr.mutex.RUnlock() 120 | return chain, ok 121 | } 122 | func (mgr *ChainInfoManager) GetChainInfoByNetcode(netcode int32) (*ChainInfo, bool) { 123 | mgr.mutex.RLock() 124 | chain, ok := mgr.netcodeChains[netcode] 125 | mgr.mutex.RUnlock() 126 | return chain, ok 127 | } 128 | 129 | func (mgr *ChainInfoManager) LoadAllChains() { 130 | // Query the database to select only id and name fields 131 | rows, err := mgr.db.Query("SELECT id, chainid, real_chainid, name, alias_name, backend, eip1559, network_code, icon, block_interval, rpc_end_point, explorer_url, official_rpc, disabled, is_testnet, order_weight, gas_token_name, gas_token_decimal, transfer_contract_address, deposit_contract_address, layer1 FROM t_chain_info") 132 | 133 | if err != nil || rows == nil { 134 | mgr.alerter.AlertText("select t_chain_info error", err) 135 | return 136 | } 137 | 138 | defer rows.Close() 139 | 140 | idChains := make(map[int64]*ChainInfo) 141 | netcodeChains := make(map[int32]*ChainInfo) 142 | chainIdChains := make(map[string]*ChainInfo) 143 | nameChains := make(map[string]*ChainInfo) 144 | 145 | counter := 0 146 | 147 | // Iterate over the result set 148 | for rows.Next() { 149 | var chain ChainInfo 150 | 151 | if err := rows.Scan(&chain.Id, &chain.ChainId, &chain.RealChainId, &chain.Name, &chain.AliasName, &chain.Backend, &chain.Eip1559, 152 | &chain.NetworkCode, &chain.Icon, &chain.BlockInterval, &chain.RpcEndPoint, &chain.ExplorerUrl, &chain.OfficialRpc, &chain.Disabled, &chain.IsTestnet, &chain.OrderWeight, 153 | &chain.GasTokenName, &chain.GasTokenDecimal, &chain.TransferContractAddress, &chain.DepositContractAddress, &chain.Layer1); err != nil { 154 | mgr.alerter.AlertText("scan t_chain_info row error", err) 155 | } else { 156 | chain.ChainId = strings.TrimSpace(chain.ChainId) 157 | chain.RealChainId = strings.TrimSpace(chain.RealChainId) 158 | chain.Name = strings.TrimSpace(chain.Name) 159 | chain.AliasName = strings.TrimSpace(chain.AliasName) 160 | chain.Icon = strings.TrimSpace(chain.Icon) 161 | chain.RpcEndPoint = strings.TrimSpace(chain.RpcEndPoint) 162 | chain.ExplorerUrl = strings.TrimSpace(chain.ExplorerUrl) 163 | chain.OfficialRpc = strings.TrimSpace(chain.OfficialRpc) 164 | chain.GasTokenName = strings.TrimSpace(chain.GasTokenName) 165 | chain.TransferContractAddress.String = strings.TrimSpace(chain.TransferContractAddress.String) 166 | chain.DepositContractAddress.String = strings.TrimSpace(chain.DepositContractAddress.String) 167 | chain.Layer1.String = strings.TrimSpace(chain.Layer1.String) 168 | 169 | if chain.Backend == EthereumBackend { 170 | chain.Client, err = ethclient.Dial(chain.RpcEndPoint) 171 | if err != nil { 172 | mgr.alerter.AlertText("create evm client error", err) 173 | continue 174 | } 175 | } else if chain.Backend == StarknetBackend { 176 | erpc, err := ethrpc.Dial(chain.RpcEndPoint) 177 | if err != nil { 178 | mgr.alerter.AlertText("create starknet client error", err) 179 | continue 180 | } 181 | chain.Client = rpc.NewProvider(erpc) 182 | } else if chain.Backend == SolanaBackend { 183 | chain.Client = solrpc.New(chain.RpcEndPoint) 184 | } 185 | 186 | idChains[chain.Id] = &chain 187 | chainIdChains[strings.ToLower(chain.ChainId)] = &chain 188 | nameChains[strings.ToLower(chain.Name)] = &chain 189 | netcodeChains[chain.NetworkCode] = &chain 190 | counter++ 191 | } 192 | } 193 | 194 | // Check for errors from iterating over rows 195 | if err := rows.Err(); err != nil { 196 | mgr.alerter.AlertText("get next t_chain_info row error", err) 197 | return 198 | } 199 | 200 | mgr.mutex.Lock() 201 | mgr.idChains = idChains 202 | mgr.chainIdChains = chainIdChains 203 | mgr.nameChains = nameChains 204 | mgr.netcodeChains = netcodeChains 205 | mgr.mutex.Unlock() 206 | log.Println("load all chain info: ", counter) 207 | 208 | } 209 | -------------------------------------------------------------------------------- /loader/channel_commission_ratio.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "sort" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type ChannelCommissionRatio struct { 13 | txCount int64 14 | ratio int64 15 | } 16 | 17 | type ChannelCommissionRatioManager struct { 18 | channelidToCountToRatio map[int64]map[int64]int64 19 | channelidToRatioArr map[int64][]ChannelCommissionRatio 20 | 21 | db *sql.DB 22 | alerter alert.Alerter 23 | mutex *sync.RWMutex 24 | } 25 | 26 | func NewChannelCommissionRatioManager(db *sql.DB, alerter alert.Alerter) *ChannelCommissionRatioManager { 27 | return &ChannelCommissionRatioManager{ 28 | channelidToCountToRatio: make(map[int64]map[int64]int64), 29 | channelidToRatioArr: make(map[int64][]ChannelCommissionRatio), 30 | 31 | db: db, 32 | alerter: alerter, 33 | mutex: &sync.RWMutex{}, 34 | } 35 | } 36 | 37 | func (mgr *ChannelCommissionRatioManager) GetRatioByChannelidAndCount(channelid int64, txcount int64) (int64, bool) { 38 | mgr.mutex.RLock() 39 | defer mgr.mutex.RUnlock() 40 | if sortList, isexist := mgr.channelidToRatioArr[channelid]; !isexist { 41 | return 0, false 42 | } else { 43 | for _, kv := range sortList { 44 | if txcount < kv.txCount { 45 | return kv.ratio, true 46 | } 47 | } 48 | return sortList[len(sortList)-1].ratio, true 49 | } 50 | } 51 | 52 | func (mgr *ChannelCommissionRatioManager) LoadAllCommissionRatio() { 53 | rows, err := mgr.db.Query("select channel_id, tx_count, commission_ratio from t_channel_commission_ratio order by tx_count asc") 54 | 55 | if err != nil || rows == nil { 56 | mgr.alerter.AlertText("select t_channel_commission_ratio error", err) 57 | return 58 | } 59 | 60 | defer rows.Close() 61 | 62 | channelidToCountToRatio := make(map[int64]map[int64]int64) 63 | channelidToRatioArr := make(map[int64][]ChannelCommissionRatio) 64 | counter := 0 65 | 66 | // Iterate over the result set 67 | for rows.Next() { 68 | var channelID, txCount, ratio int64 69 | 70 | if err := rows.Scan(&channelID, &txCount, &ratio); err != nil { 71 | mgr.alerter.AlertText("scan t_channel_commission_ratio row error", err) 72 | } else { 73 | channelidToCountToRatio[channelID] = make(map[int64]int64) 74 | channelidToCountToRatio[channelID][txCount] = ratio 75 | 76 | ratioArr, exist := channelidToRatioArr[channelID] 77 | if !exist { 78 | ratioArr = make([]ChannelCommissionRatio, 0) 79 | } 80 | ratioArr = append(ratioArr, ChannelCommissionRatio{txCount, ratio}) 81 | channelidToRatioArr[channelID] = ratioArr 82 | 83 | counter++ 84 | } 85 | } 86 | 87 | // Check for errors from iterating over rows 88 | if err := rows.Err(); err != nil { 89 | mgr.alerter.AlertText("get next t_channel_commission_ratio row error", err) 90 | return 91 | } 92 | 93 | for k, _ := range channelidToRatioArr { 94 | sort.Slice(channelidToRatioArr[k], func(i, j int) bool { 95 | return channelidToRatioArr[k][i].txCount < channelidToRatioArr[k][j].txCount 96 | }) 97 | } 98 | 99 | mgr.mutex.Lock() 100 | mgr.channelidToCountToRatio = channelidToCountToRatio 101 | mgr.channelidToRatioArr = channelidToRatioArr 102 | mgr.mutex.Unlock() 103 | log.Println("load all channel commission ratio: ", counter) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /loader/circle_cctp_chain.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/owlto-finance/utils-go/alert" 12 | ) 13 | 14 | type CircleCctpChain struct { 15 | ChainId int32 16 | MinValue string 17 | Domain int32 18 | TokenMessenger string 19 | MessageTransmitter string 20 | } 21 | 22 | func (ccc *CircleCctpChain) GetMinValueUnit() *big.Int { 23 | min, _ := new(big.Int).SetString(ccc.MinValue, 0) 24 | result := new(big.Int).Mul(min, big.NewInt(1000000)) 25 | return result 26 | } 27 | 28 | type CircleCctpChainManager struct { 29 | chainIdChains map[int32]*CircleCctpChain 30 | db *sql.DB 31 | alerter alert.Alerter 32 | mutex *sync.RWMutex 33 | } 34 | 35 | func NewCircleCctpChainManager(db *sql.DB, alerter alert.Alerter) *CircleCctpChainManager { 36 | return &CircleCctpChainManager{ 37 | chainIdChains: make(map[int32]*CircleCctpChain), 38 | db: db, 39 | alerter: alerter, 40 | mutex: &sync.RWMutex{}, 41 | } 42 | } 43 | 44 | func (mgr *CircleCctpChainManager) GetDtcUnit(srcChainId int32, dstChainId int32) *big.Int { 45 | if srcChainId == 1 || dstChainId == 1 { 46 | return big.NewInt(30000000) 47 | } 48 | return big.NewInt(5000000) 49 | } 50 | 51 | func (mgr *CircleCctpChainManager) GetChainByChainId(id int32) (*CircleCctpChain, bool) { 52 | mgr.mutex.RLock() 53 | chain, ok := mgr.chainIdChains[id] 54 | mgr.mutex.RUnlock() 55 | return chain, ok 56 | } 57 | 58 | func (mgr *CircleCctpChainManager) GetChainIds() []int32 { 59 | mgr.mutex.RLock() 60 | chainIds := make([]int32, 0, len(mgr.chainIdChains)) 61 | 62 | // Iterate over the map and extract keys 63 | for chainId := range mgr.chainIdChains { 64 | chainIds = append(chainIds, chainId) 65 | } 66 | mgr.mutex.RUnlock() 67 | return chainIds 68 | } 69 | 70 | func (mgr *CircleCctpChainManager) LoadAllChains() { 71 | // Query the database to select only id and name fields 72 | rows, err := mgr.db.Query("SELECT chainid, min_value, domain, token_messenger, message_transmitter FROM t_cctp_support_chain") 73 | 74 | if err != nil || rows == nil { 75 | mgr.alerter.AlertText("select t_cctp_support_chain error", err) 76 | return 77 | } 78 | 79 | defer rows.Close() 80 | 81 | chainIdChains := make(map[int32]*CircleCctpChain) 82 | counter := 0 83 | 84 | // Iterate over the result set 85 | for rows.Next() { 86 | var chain CircleCctpChain 87 | if err := rows.Scan(&chain.ChainId, &chain.MinValue, &chain.Domain, &chain.TokenMessenger, &chain.MessageTransmitter); err != nil { 88 | mgr.alerter.AlertText("scan t_cctp_support_chain row error", err) 89 | } else { 90 | chain.MessageTransmitter = strings.TrimSpace(chain.MessageTransmitter) 91 | chain.TokenMessenger = strings.TrimSpace(chain.TokenMessenger) 92 | chain.MinValue = strings.TrimSpace(chain.MinValue) 93 | _, ok := new(big.Int).SetString(chain.MinValue, 0) 94 | if !ok { 95 | mgr.alerter.AlertText("scan t_cctp_support_chain min value error ", fmt.Errorf("id: %d, min value: %s", chain.ChainId, chain.MinValue)) 96 | continue 97 | } 98 | 99 | chainIdChains[chain.ChainId] = &chain 100 | counter++ 101 | } 102 | } 103 | 104 | // Check for errors from iterating over rows 105 | if err := rows.Err(); err != nil { 106 | mgr.alerter.AlertText("get next t_cctp_support_chain row error", err) 107 | return 108 | } 109 | 110 | mgr.mutex.Lock() 111 | mgr.chainIdChains = chainIdChains 112 | mgr.mutex.Unlock() 113 | log.Println("load all cctp chain: ", counter) 114 | 115 | } 116 | -------------------------------------------------------------------------------- /loader/dst_tx.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "strings" 6 | 7 | "github.com/owlto-finance/utils-go/alert" 8 | ) 9 | 10 | type DstTx struct { 11 | SrcAction string 12 | SrcId int64 13 | SrcVersion int32 14 | Sender int64 15 | Body string 16 | FeeCap sql.NullString 17 | TransferToken sql.NullString 18 | TransferRecipient sql.NullString 19 | TransferAmount sql.NullString 20 | } 21 | 22 | type TxGen struct { 23 | Id int64 24 | Hash string 25 | ConfirmedSuccess int8 26 | } 27 | 28 | type DstTxManager struct { 29 | db *sql.DB 30 | alerter alert.Alerter 31 | //mutex *sync.RWMutex 32 | } 33 | 34 | func NewDstTxManager(db *sql.DB, alerter alert.Alerter) *DstTxManager { 35 | return &DstTxManager{ 36 | db: db, 37 | alerter: alerter, 38 | //mutex: &sync.RWMutex{}, 39 | } 40 | } 41 | 42 | func (mgr *DstTxManager) GetDoneTxGenBySrc(srcId int64, action string, version int32) *TxGen { 43 | genId := mgr.GetDstTxConfirmGen(srcId, action, version) 44 | if genId == 0 { 45 | return nil 46 | } 47 | return mgr.GetDoneTxGen(genId) 48 | } 49 | 50 | func (mgr *DstTxManager) GetDoneTxGen(genId int64) *TxGen { 51 | var gen TxGen 52 | err := mgr.db.QueryRow("SELECT id,hash, confirmed_success FROM t_dst_transaction_gen where id = ? and confirmed_success is not null", genId).Scan(&gen.Id, &gen.Hash, &gen.ConfirmedSuccess) 53 | if err != nil { 54 | return nil 55 | } 56 | gen.Hash = strings.TrimSpace(gen.Hash) 57 | return &gen 58 | } 59 | 60 | func (mgr *DstTxManager) IsDstTxExist(srcId int64, action string, version int32) bool { 61 | var id int64 62 | err := mgr.db.QueryRow("SELECT id FROM t_dst_transaction where src_action = ? and src_id = ? and src_version = ?", strings.TrimSpace(action), srcId, version).Scan(&id) 63 | return err == nil 64 | } 65 | 66 | func (mgr *DstTxManager) GetDstTxConfirmGen(srcId int64, action string, version int32) int64 { 67 | var genId int64 = 0 68 | err := mgr.db.QueryRow("SELECT confirmed_gen FROM t_dst_transaction where src_action = ? and src_id = ? and src_version = ? and confirmed_gen is not null", strings.TrimSpace(action), srcId, version).Scan(&genId) 69 | if err != nil { 70 | return 0 71 | } 72 | return genId 73 | } 74 | 75 | func (mgr *DstTxManager) Save(tx *DstTx) error { 76 | tx.SrcAction = strings.TrimSpace(tx.SrcAction) 77 | tx.Body = strings.TrimSpace(tx.Body) 78 | tx.FeeCap.String = strings.TrimSpace(tx.FeeCap.String) 79 | tx.TransferToken.String = strings.TrimSpace(tx.TransferToken.String) 80 | tx.TransferRecipient.String = strings.TrimSpace(tx.TransferRecipient.String) 81 | tx.TransferAmount.String = strings.TrimSpace(tx.TransferAmount.String) 82 | 83 | query := `INSERT IGNORE INTO t_dst_transaction (src_action, src_id, src_version, sender, body, fee_cap, transfer_token, transfer_recipient, transfer_amount) 84 | VALUES (?, ?, ?, ?, ?, ?, ? , ?, ?)` 85 | 86 | // Execute the SQL statement with tx data 87 | _, err := mgr.db.Exec(query, tx.SrcAction, tx.SrcId, tx.SrcVersion, tx.Sender, tx.Body, tx.FeeCap, tx.TransferToken, tx.TransferRecipient, tx.TransferAmount) 88 | if err != nil { 89 | mgr.alerter.AlertText("failed to insert dst transaction", err) 90 | return err 91 | } 92 | 93 | return nil 94 | 95 | } 96 | -------------------------------------------------------------------------------- /loader/dtc.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/owlto-finance/utils-go/alert" 12 | "github.com/owlto-finance/utils-go/util" 13 | ) 14 | 15 | type Dtc struct { 16 | TokenName string 17 | FromChainName string 18 | ToChainName string 19 | DtcLv1 float64 20 | DtcLv2 float64 21 | DtcLv3 float64 22 | DtcLv4 float64 23 | AmountLv1 float64 24 | AmountLv2 float64 25 | AmountLv3 float64 26 | AmountLv4 float64 27 | 28 | DtcLv1Str string 29 | DtcLv2Str string 30 | DtcLv3Str string 31 | DtcLv4Str string 32 | AmountLv1Str string 33 | AmountLv2Str string 34 | AmountLv3Str string 35 | AmountLv4Str string 36 | } 37 | 38 | type DtcManager struct { 39 | tokenFromToDtcs map[string]map[string]map[string]*Dtc 40 | 41 | db *sql.DB 42 | alerter alert.Alerter 43 | mutex *sync.RWMutex 44 | } 45 | 46 | func NewDtcManager(db *sql.DB, alerter alert.Alerter) *DtcManager { 47 | return &DtcManager{ 48 | tokenFromToDtcs: make(map[string]map[string]map[string]*Dtc), 49 | 50 | db: db, 51 | alerter: alerter, 52 | mutex: &sync.RWMutex{}, 53 | } 54 | } 55 | 56 | func (mgr *DtcManager) GetDtcs() map[string]map[string]map[string]*Dtc { 57 | mgr.mutex.RLock() 58 | defer mgr.mutex.RUnlock() 59 | return mgr.tokenFromToDtcs 60 | } 61 | 62 | func (mgr *DtcManager) GetDtc(token string, from string, to string) (*Dtc, bool) { 63 | mgr.mutex.RLock() 64 | defer mgr.mutex.RUnlock() 65 | ftInfos, ok := mgr.tokenFromToDtcs[strings.ToLower(strings.TrimSpace(token))] 66 | if ok { 67 | infos, ok := ftInfos[strings.ToLower(strings.TrimSpace(from))] 68 | if ok { 69 | info, ok := infos[strings.ToLower(strings.TrimSpace(to))] 70 | return info, ok 71 | } 72 | 73 | } 74 | return nil, false 75 | } 76 | 77 | func (mgr *DtcManager) LoadAllDtc() { 78 | // Query the database to select only id and name fields 79 | rows, err := mgr.db.Query("SELECT token_name, from_chain, to_chain, dtc_lv1, dtc_lv2, dtc_lv3, dtc_lv4, amount_lv1, amount_lv2, amount_lv3, amount_lv4 FROM t_dynamic_dtc") 80 | 81 | if err != nil || rows == nil { 82 | mgr.alerter.AlertText("select t_dynamic_dtc error", err) 83 | return 84 | } 85 | 86 | defer rows.Close() 87 | 88 | tokenFromToDtcs := make(map[string]map[string]map[string]*Dtc) 89 | counter := 0 90 | 91 | // Iterate over the result set 92 | for rows.Next() { 93 | var dtc Dtc 94 | 95 | if err := rows.Scan(&dtc.TokenName, &dtc.FromChainName, &dtc.ToChainName, &dtc.DtcLv1Str, &dtc.DtcLv2Str, &dtc.DtcLv3Str, &dtc.DtcLv4Str, &dtc.AmountLv1Str, &dtc.AmountLv2Str, &dtc.AmountLv3Str, &dtc.AmountLv4Str); err != nil { 96 | mgr.alerter.AlertText("scan t_dynamic_dtc row error", err) 97 | } else { 98 | dtc.FromChainName = strings.TrimSpace(dtc.FromChainName) 99 | dtc.ToChainName = strings.TrimSpace(dtc.ToChainName) 100 | dtc.TokenName = strings.TrimSpace(dtc.TokenName) 101 | 102 | dtc1, err := strconv.ParseFloat(dtc.DtcLv1Str, 64) 103 | if err != nil { 104 | mgr.alerter.AlertText("t_dynamic_dtc dtc1 not float", err) 105 | continue 106 | } 107 | dtc2, err := strconv.ParseFloat(dtc.DtcLv2Str, 64) 108 | if err != nil { 109 | mgr.alerter.AlertText("t_dynamic_dtc dtc2 not float", err) 110 | continue 111 | } 112 | dtc3, err := strconv.ParseFloat(dtc.DtcLv3Str, 64) 113 | if err != nil { 114 | mgr.alerter.AlertText("t_dynamic_dtc dtc3 not float", err) 115 | continue 116 | } 117 | dtc4, err := strconv.ParseFloat(dtc.DtcLv4Str, 64) 118 | if err != nil { 119 | mgr.alerter.AlertText("t_dynamic_dtc dtc4 not float", err) 120 | continue 121 | } 122 | 123 | amount1, err := strconv.ParseFloat(dtc.AmountLv1Str, 64) 124 | if err != nil { 125 | mgr.alerter.AlertText("t_dynamic_dtc amount1 not float", err) 126 | continue 127 | } 128 | amount2, err := strconv.ParseFloat(dtc.AmountLv2Str, 64) 129 | if err != nil { 130 | mgr.alerter.AlertText("t_dynamic_dtc amount2 not float", err) 131 | continue 132 | } 133 | amount3, err := strconv.ParseFloat(dtc.AmountLv3Str, 64) 134 | if err != nil { 135 | mgr.alerter.AlertText("t_dynamic_dtc amount3 not float", err) 136 | continue 137 | } 138 | amount4, err := strconv.ParseFloat(dtc.AmountLv4Str, 64) 139 | if err != nil { 140 | mgr.alerter.AlertText("t_dynamic_dtc amount4 not float", err) 141 | continue 142 | } 143 | 144 | dtc.DtcLv1 = dtc1 145 | dtc.DtcLv2 = dtc2 146 | dtc.DtcLv3 = dtc3 147 | dtc.DtcLv4 = dtc4 148 | dtc.AmountLv1 = amount1 149 | dtc.AmountLv2 = amount2 150 | dtc.AmountLv3 = amount3 151 | dtc.AmountLv4 = amount4 152 | 153 | ftInfos, ok := tokenFromToDtcs[strings.ToLower(dtc.TokenName)] 154 | if !ok { 155 | ftInfos = make(map[string]map[string]*Dtc) 156 | tokenFromToDtcs[strings.ToLower(dtc.TokenName)] = ftInfos 157 | } 158 | infos, ok := ftInfos[strings.ToLower(dtc.FromChainName)] 159 | if !ok { 160 | infos = make(map[string]*Dtc) 161 | ftInfos[strings.ToLower(dtc.FromChainName)] = infos 162 | } 163 | infos[strings.ToLower(dtc.ToChainName)] = &dtc 164 | 165 | counter++ 166 | } 167 | } 168 | 169 | // Check for errors from iterating over rows 170 | if err := rows.Err(); err != nil { 171 | mgr.alerter.AlertText("get next t_dynamic_dtc row error", err) 172 | return 173 | } 174 | 175 | mgr.mutex.Lock() 176 | mgr.tokenFromToDtcs = tokenFromToDtcs 177 | mgr.mutex.Unlock() 178 | log.Println("load all dtc: ", counter) 179 | 180 | } 181 | 182 | func (mgr *DtcManager) GetIncludedDtc(tokenName string, fromChainName string, toChainName string, value float64) (float64, string, bool) { 183 | dtc, ok := mgr.GetDtc(tokenName, fromChainName, toChainName) 184 | if !ok { 185 | return 0, "", false 186 | } 187 | 188 | if value < (dtc.AmountLv1 + dtc.DtcLv1) { 189 | return dtc.DtcLv1, dtc.DtcLv1Str, true 190 | } else if value < (dtc.AmountLv2 + dtc.DtcLv2) { 191 | return dtc.DtcLv2, dtc.DtcLv2Str, true 192 | } else if value < (dtc.AmountLv3 + dtc.DtcLv3) { 193 | return dtc.DtcLv3, dtc.DtcLv3Str, true 194 | } else { 195 | return dtc.DtcLv4, dtc.DtcLv4Str, true 196 | } 197 | } 198 | 199 | func (mgr *DtcManager) GetDtcToInclude(tokenName string, fromChainName string, toChainName string, value float64) (float64, string, bool) { 200 | dtc, ok := mgr.GetDtc(tokenName, fromChainName, toChainName) 201 | if !ok { 202 | return 0, "", false 203 | } 204 | 205 | if value < dtc.AmountLv1 { 206 | return dtc.DtcLv1, dtc.DtcLv1Str, true 207 | } else if value < dtc.AmountLv2 { 208 | return dtc.DtcLv2, dtc.DtcLv2Str, true 209 | } else if value < dtc.AmountLv3 { 210 | return dtc.DtcLv3, dtc.DtcLv3Str, true 211 | } else { 212 | return dtc.DtcLv4, dtc.DtcLv4Str, true 213 | } 214 | } 215 | 216 | func (mgr *DtcManager) FromUiString(amount string, dtc string, decimals int32) *big.Int { 217 | value := big.NewInt(0) 218 | if amount != "" { 219 | amountValue, err := util.FromUiString(amount, decimals) 220 | if err == nil { 221 | value.Add(value, amountValue) 222 | } 223 | } 224 | 225 | if dtc != "" { 226 | dtcValue, err := util.FromUiString(dtc, decimals) 227 | if err == nil { 228 | value.Add(value, dtcValue) 229 | } 230 | } 231 | return value 232 | } 233 | 234 | func (mgr *DtcManager) GetIncludedDtcBigInt(tokenName string, fromChainName string, toChainName string, value *big.Int, decimals int32) (*big.Int, bool) { 235 | dtc, ok := mgr.GetDtc(tokenName, fromChainName, toChainName) 236 | if !ok { 237 | return nil, false 238 | } 239 | 240 | if value.Cmp(mgr.FromUiString(dtc.AmountLv1Str, dtc.DtcLv1Str, decimals)) < 0 { 241 | return mgr.FromUiString("", dtc.DtcLv1Str, decimals), true 242 | } else if value.Cmp(mgr.FromUiString(dtc.AmountLv2Str, dtc.DtcLv2Str, decimals)) < 0 { 243 | return mgr.FromUiString("", dtc.DtcLv2Str, decimals), true 244 | } else if value.Cmp(mgr.FromUiString(dtc.AmountLv3Str, dtc.DtcLv3Str, decimals)) < 0 { 245 | return mgr.FromUiString("", dtc.DtcLv3Str, decimals), true 246 | } else { 247 | return mgr.FromUiString("", dtc.DtcLv4Str, decimals), true 248 | } 249 | } 250 | 251 | func (mgr *DtcManager) GetDtcToIncludeBigInt(tokenName string, fromChainName string, toChainName string, value *big.Int, decimals int32) (*big.Int, bool) { 252 | dtc, ok := mgr.GetDtc(tokenName, fromChainName, toChainName) 253 | if !ok { 254 | return nil, false 255 | } 256 | 257 | if value.Cmp(mgr.FromUiString(dtc.AmountLv1Str, "", decimals)) < 0 { 258 | return mgr.FromUiString("", dtc.DtcLv1Str, decimals), true 259 | } else if value.Cmp(mgr.FromUiString(dtc.AmountLv2Str, "", decimals)) < 0 { 260 | return mgr.FromUiString("", dtc.DtcLv2Str, decimals), true 261 | } else if value.Cmp(mgr.FromUiString(dtc.AmountLv3Str, "", decimals)) < 0 { 262 | return mgr.FromUiString("", dtc.DtcLv3Str, decimals), true 263 | } else { 264 | return mgr.FromUiString("", dtc.DtcLv4Str, decimals), true 265 | } 266 | } 267 | 268 | func (mgr *DtcManager) GetMinValueIncludeGasFee(tokenName string, fromChainName string, toChainName string, decimals int32) (string, bool) { 269 | dtc, ok := mgr.GetDtc(tokenName, fromChainName, toChainName) 270 | if !ok { 271 | return "", false 272 | } 273 | 274 | vals := []string{dtc.DtcLv1Str, dtc.DtcLv2Str, dtc.DtcLv3Str, dtc.DtcLv4Str} 275 | for _, val := range vals { 276 | value := mgr.FromUiString("", val, decimals) 277 | includedDtc, ok := mgr.GetIncludedDtcBigInt(tokenName, fromChainName, toChainName, value, decimals) 278 | if ok { 279 | if includedDtc.Cmp(value) <= 0 { 280 | return val, true 281 | } 282 | } 283 | } 284 | return "", false 285 | } 286 | -------------------------------------------------------------------------------- /loader/exchange_info.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type ExchangeInfo struct { 13 | Id int32 14 | Name string 15 | Icon string 16 | Disabled int8 17 | OfficialUrl string 18 | OrderWeight int32 19 | } 20 | 21 | type ExchangeInfoManager struct { 22 | idExchanges map[int32]*ExchangeInfo 23 | nameExchanges map[string]*ExchangeInfo 24 | allExchanges []*ExchangeInfo 25 | db *sql.DB 26 | alerter alert.Alerter 27 | mutex *sync.RWMutex 28 | } 29 | 30 | func NewExchangeInfoManager(db *sql.DB, alerter alert.Alerter) *ExchangeInfoManager { 31 | return &ExchangeInfoManager{ 32 | idExchanges: make(map[int32]*ExchangeInfo), 33 | nameExchanges: make(map[string]*ExchangeInfo), 34 | allExchanges: make([]*ExchangeInfo, 0, 100), 35 | db: db, 36 | alerter: alerter, 37 | mutex: &sync.RWMutex{}, 38 | } 39 | } 40 | 41 | func (mgr *ExchangeInfoManager) GetAllExchanges() []*ExchangeInfo { 42 | mgr.mutex.RLock() 43 | defer mgr.mutex.RUnlock() 44 | infos := make([]*ExchangeInfo, len(mgr.allExchanges)) 45 | copy(infos, mgr.allExchanges) 46 | return infos 47 | } 48 | 49 | func (mgr *ExchangeInfoManager) GetExchangeInfoById(id int32) (*ExchangeInfo, bool) { 50 | mgr.mutex.RLock() 51 | xchg, ok := mgr.idExchanges[id] 52 | mgr.mutex.RUnlock() 53 | return xchg, ok 54 | } 55 | func (mgr *ExchangeInfoManager) GetExchangeInfoByName(name string) (*ExchangeInfo, bool) { 56 | mgr.mutex.RLock() 57 | xchg, ok := mgr.nameExchanges[strings.ToLower(strings.TrimSpace(name))] 58 | mgr.mutex.RUnlock() 59 | return xchg, ok 60 | } 61 | 62 | func (mgr *ExchangeInfoManager) LoadAllExchanges() { 63 | // Query the database to select only id and name fields 64 | rows, err := mgr.db.Query("SELECT id, name, icon, disabled, official_url, order_weight FROM t_exchange_info") 65 | 66 | if err != nil || rows == nil { 67 | mgr.alerter.AlertText("select t_exchange_info error", err) 68 | return 69 | } 70 | 71 | defer rows.Close() 72 | 73 | idExchanges := make(map[int32]*ExchangeInfo) 74 | nameExchanges := make(map[string]*ExchangeInfo) 75 | allExchanges := make([]*ExchangeInfo, 0, 100) 76 | counter := 0 77 | 78 | // Iterate over the result set 79 | for rows.Next() { 80 | var xchg ExchangeInfo 81 | if err := rows.Scan(&xchg.Id, &xchg.Name, &xchg.Icon, &xchg.Disabled, &xchg.OfficialUrl, &xchg.OrderWeight); err != nil { 82 | mgr.alerter.AlertText("scan t_exchange_info row error", err) 83 | } else { 84 | xchg.Name = strings.TrimSpace(xchg.Name) 85 | idExchanges[xchg.Id] = &xchg 86 | nameExchanges[strings.ToLower(xchg.Name)] = &xchg 87 | allExchanges = append(allExchanges, &xchg) 88 | counter++ 89 | } 90 | } 91 | 92 | // Check for errors from iterating over rows 93 | if err := rows.Err(); err != nil { 94 | mgr.alerter.AlertText("get next t_exchange_info row error", err) 95 | return 96 | } 97 | 98 | mgr.mutex.Lock() 99 | mgr.idExchanges = idExchanges 100 | mgr.nameExchanges = nameExchanges 101 | mgr.allExchanges = allExchanges 102 | mgr.mutex.Unlock() 103 | log.Println("load all exchanges : ", counter) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /loader/lp_info.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/owlto-finance/utils-go/alert" 11 | ) 12 | 13 | const ( 14 | LpInfoVersion int32 = 1 15 | ) 16 | 17 | type LpInfo struct { 18 | Version int32 19 | TokenName string 20 | FromChainName string 21 | ToChainName string 22 | MinValue float64 23 | MaxValue float64 24 | BridgeFeeRatio float64 25 | MinValueStr string 26 | MaxValueStr string 27 | BridgeFeeRatioStr string 28 | MakerAddress string 29 | IsDisabled int32 30 | } 31 | 32 | type LpInfoManager struct { 33 | lpInfos map[int32]map[string]map[string]map[string]map[string]*LpInfo 34 | allLpInfos []*LpInfo 35 | db *sql.DB 36 | alerter alert.Alerter 37 | mutex *sync.RWMutex 38 | } 39 | 40 | func NewLpInfoManager(db *sql.DB, alerter alert.Alerter) *LpInfoManager { 41 | return &LpInfoManager{ 42 | lpInfos: make(map[int32]map[string]map[string]map[string]map[string]*LpInfo), 43 | allLpInfos: make([]*LpInfo, 0, 100), 44 | db: db, 45 | alerter: alerter, 46 | mutex: &sync.RWMutex{}, 47 | } 48 | } 49 | 50 | func (mgr *LpInfoManager) GetAllLpInfos() []*LpInfo { 51 | mgr.mutex.RLock() 52 | defer mgr.mutex.RUnlock() 53 | infos := make([]*LpInfo, len(mgr.allLpInfos)) 54 | copy(infos, mgr.allLpInfos) 55 | return infos 56 | } 57 | 58 | func (mgr *LpInfoManager) GetLpInfos(version int32, token string, from string, to string) (map[string]*LpInfo, bool) { 59 | mgr.mutex.RLock() 60 | defer mgr.mutex.RUnlock() 61 | 62 | versionInfos, ok := mgr.lpInfos[version] 63 | if ok { 64 | ftInfos, ok := versionInfos[strings.ToLower(strings.TrimSpace(token))] 65 | if ok { 66 | infos, ok := ftInfos[strings.ToLower(strings.TrimSpace(from))] 67 | if ok { 68 | info, ok := infos[strings.ToLower(strings.TrimSpace(to))] 69 | return info, ok 70 | } 71 | 72 | } 73 | } 74 | return nil, false 75 | } 76 | 77 | func (mgr *LpInfoManager) GetLpInfo(version int32, token string, from string, to string, maker string) (*LpInfo, bool) { 78 | mgr.mutex.RLock() 79 | defer mgr.mutex.RUnlock() 80 | 81 | versionInfos, ok := mgr.lpInfos[version] 82 | if ok { 83 | ftInfos, ok := versionInfos[strings.ToLower(strings.TrimSpace(token))] 84 | if ok { 85 | infos, ok := ftInfos[strings.ToLower(strings.TrimSpace(from))] 86 | if ok { 87 | info, ok := infos[strings.ToLower(strings.TrimSpace(to))] 88 | if ok { 89 | maker, ok := info[strings.ToLower(strings.TrimSpace(maker))] 90 | return maker, ok 91 | } 92 | } 93 | 94 | } 95 | } 96 | return nil, false 97 | } 98 | 99 | func (mgr *LpInfoManager) GetTokensByLp(version int32, from string, to string) ([]string, bool) { 100 | mgr.mutex.RLock() 101 | defer mgr.mutex.RUnlock() 102 | 103 | versionInfos, ok := mgr.lpInfos[version] 104 | var getTokensByLp []string 105 | 106 | if !ok { 107 | return nil, false 108 | } 109 | 110 | parmaFrom := strings.ToLower(strings.TrimSpace(from)) 111 | paramTo := strings.ToLower(strings.TrimSpace(to)) 112 | 113 | for tokenName, ftInfos := range versionInfos { 114 | 115 | for fromName, ftInfo := range ftInfos { 116 | if fromName != parmaFrom { 117 | continue 118 | } 119 | 120 | for toName, _ := range ftInfo { 121 | if toName != paramTo { 122 | continue 123 | } 124 | getTokensByLp = append(getTokensByLp, strings.ToUpper(tokenName)) 125 | break 126 | } 127 | } 128 | 129 | } 130 | return getTokensByLp, true 131 | } 132 | 133 | func (mgr *LpInfoManager) LoadAllLpInfo() { 134 | // Query the database to select only id and name fields 135 | rows, err := mgr.db.Query("SELECT version, token_name, from_chain, to_chain, maker_address, min_value, max_value, is_disabled, bridge_fee_ratio FROM t_lp_info") 136 | 137 | if err != nil || rows == nil { 138 | mgr.alerter.AlertText("select t_lp_info error", err) 139 | return 140 | } 141 | 142 | defer rows.Close() 143 | 144 | lpInfos := make(map[int32]map[string]map[string]map[string]map[string]*LpInfo) 145 | allLpInfos := make([]*LpInfo, 0, 100) 146 | counter := 0 147 | 148 | // Iterate over the result set 149 | for rows.Next() { 150 | var info LpInfo 151 | if err := rows.Scan(&info.Version, &info.TokenName, &info.FromChainName, &info.ToChainName, &info.MakerAddress, &info.MinValueStr, &info.MaxValueStr, &info.IsDisabled, &info.BridgeFeeRatioStr); err != nil { 152 | mgr.alerter.AlertText("scan t_lp_info row error", err) 153 | } else { 154 | 155 | info.FromChainName = strings.TrimSpace(info.FromChainName) 156 | info.ToChainName = strings.TrimSpace(info.ToChainName) 157 | info.TokenName = strings.TrimSpace(info.TokenName) 158 | info.MakerAddress = strings.TrimSpace(info.MakerAddress) 159 | 160 | min, err := strconv.ParseFloat(info.MinValueStr, 64) 161 | if err != nil { 162 | mgr.alerter.AlertText("t_lp_info min not float", err) 163 | continue 164 | } 165 | max, err := strconv.ParseFloat(info.MaxValueStr, 64) 166 | if err != nil { 167 | mgr.alerter.AlertText("t_lp_info max not float", err) 168 | continue 169 | } 170 | bdgfee, err := strconv.ParseFloat(info.BridgeFeeRatioStr, 64) 171 | if err != nil { 172 | mgr.alerter.AlertText("t_lp_info bridge fee not float", err) 173 | continue 174 | } 175 | 176 | info.MinValue = min 177 | info.MaxValue = max 178 | info.BridgeFeeRatio = bdgfee 179 | 180 | versions, ok := lpInfos[info.Version] 181 | if !ok { 182 | versions = make(map[string]map[string]map[string]map[string]*LpInfo) 183 | lpInfos[info.Version] = versions 184 | } 185 | 186 | ftInfos, ok := versions[strings.ToLower(info.TokenName)] 187 | if !ok { 188 | ftInfos = make(map[string]map[string]map[string]*LpInfo) 189 | versions[strings.ToLower(info.TokenName)] = ftInfos 190 | } 191 | infos, ok := ftInfos[strings.ToLower(info.FromChainName)] 192 | if !ok { 193 | infos = make(map[string]map[string]*LpInfo) 194 | ftInfos[strings.ToLower(info.FromChainName)] = infos 195 | } 196 | makers, ok := infos[strings.ToLower(info.ToChainName)] 197 | if !ok { 198 | makers = make(map[string]*LpInfo) 199 | infos[strings.ToLower(info.ToChainName)] = makers 200 | } 201 | makers[strings.ToLower(info.MakerAddress)] = &info 202 | allLpInfos = append(allLpInfos, &info) 203 | counter++ 204 | } 205 | } 206 | 207 | // Check for errors from iterating over rows 208 | if err := rows.Err(); err != nil { 209 | mgr.alerter.AlertText("get next t_lp_info row error", err) 210 | return 211 | } 212 | 213 | mgr.mutex.Lock() 214 | mgr.lpInfos = lpInfos 215 | mgr.allLpInfos = allLpInfos 216 | mgr.mutex.Unlock() 217 | log.Println("load all lp info: ", counter) 218 | } 219 | -------------------------------------------------------------------------------- /loader/maker_address.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/owlto-finance/utils-go/log" 6 | ) 7 | 8 | type MakerAddressGroupPO struct { 9 | Id int64 10 | GroupName string 11 | Env string 12 | } 13 | 14 | type MakerAddressPO struct { 15 | Id int64 16 | GroupId int64 17 | Backend Backend 18 | Address string 19 | } 20 | 21 | type MakerAddress struct { 22 | GroupId int64 23 | GroupName string 24 | Env string 25 | Addresses []*MakerAddressPO 26 | } 27 | 28 | type MakerAddressManager struct { 29 | groupIdAddress map[int64]*MakerAddress 30 | envGroup map[string][]*MakerAddress 31 | backendAddressToGroup map[Backend]map[string]int64 32 | 33 | db *sql.DB 34 | } 35 | 36 | func NewMakerAddressManager(db *sql.DB) *MakerAddressManager { 37 | return &MakerAddressManager{ 38 | groupIdAddress: make(map[int64]*MakerAddress), 39 | envGroup: make(map[string][]*MakerAddress), 40 | backendAddressToGroup: make(map[Backend]map[string]int64), 41 | db: db, 42 | } 43 | } 44 | 45 | func (mgr *MakerAddressManager) LoadAllMakerAddresses() { 46 | // Query the database for all maker address groups 47 | groupRows, err := mgr.db.Query("SELECT id, group_name, env FROM t_maker_address_groups") 48 | if err != nil || groupRows == nil { 49 | log.Errorf("select maker_address_groups error: %v", err) 50 | return 51 | } 52 | defer groupRows.Close() 53 | 54 | groups := make(map[int64]*MakerAddress) 55 | for groupRows.Next() { 56 | var group MakerAddressGroupPO 57 | if err = groupRows.Scan(&group.Id, &group.GroupName, &group.Env); err != nil { 58 | log.Errorf("scan maker_address_groups row error: %v", err) 59 | continue 60 | } 61 | 62 | makerAddress := &MakerAddress{ 63 | GroupId: group.Id, 64 | GroupName: group.GroupName, 65 | Env: group.Env, 66 | Addresses: []*MakerAddressPO{}, 67 | } 68 | groups[group.Id] = makerAddress 69 | } 70 | 71 | // Check for errors from iterating over rows 72 | if err = groupRows.Err(); err != nil { 73 | log.Errorf("get next maker_address_groups row error: %v", err) 74 | return 75 | } 76 | 77 | // Query the database for all maker addresses 78 | addressRows, err := mgr.db.Query("SELECT id, group_id, backend, address FROM t_maker_addresses") 79 | if err != nil || addressRows == nil { 80 | log.Errorf("select maker_addresses error: %v", err) 81 | return 82 | } 83 | defer addressRows.Close() 84 | 85 | backendAddressToGroup := make(map[Backend]map[string]int64) 86 | 87 | for addressRows.Next() { 88 | var address MakerAddressPO 89 | if err = addressRows.Scan(&address.Id, &address.GroupId, &address.Backend, &address.Address); err != nil { 90 | log.Errorf("scan maker_addresses row error: %v", err) 91 | continue 92 | } 93 | 94 | if group, ok := groups[address.GroupId]; ok { 95 | group.Addresses = append(group.Addresses, &address) 96 | } 97 | 98 | // Populate tempBackendAddressToGroup mapping 99 | if _, ok := backendAddressToGroup[address.Backend]; !ok { 100 | backendAddressToGroup[address.Backend] = make(map[string]int64) 101 | } 102 | backendAddressToGroup[address.Backend][address.Address] = address.GroupId 103 | } 104 | 105 | if err = addressRows.Err(); err != nil { 106 | log.Errorf("get next maker_addresses row error: %v", err) 107 | return 108 | } 109 | 110 | mgr.groupIdAddress = groups 111 | envGroup := make(map[string][]*MakerAddress) 112 | for _, group := range groups { 113 | envGroup[group.Env] = append(envGroup[group.Env], group) 114 | } 115 | mgr.envGroup = envGroup 116 | mgr.backendAddressToGroup = backendAddressToGroup 117 | 118 | log.Infof("load all maker addresses: %d", len(groups)) 119 | } 120 | 121 | func (mgr *MakerAddressManager) GetMakerAddressesByEnv(env string) []*MakerAddress { 122 | return mgr.envGroup[env] 123 | } 124 | 125 | func (mgr *MakerAddressManager) GetMakerAddressByGroupId(groupId int64) *MakerAddress { 126 | return mgr.groupIdAddress[groupId] 127 | } 128 | 129 | func (mgr *MakerAddressManager) GetGroupIDByBackendAndAddress(backend Backend, address string) int64 { 130 | if addressMap, ok := mgr.backendAddressToGroup[backend]; ok { 131 | if groupId, ok := addressMap[address]; ok { 132 | return groupId 133 | } 134 | } 135 | return 0 136 | } 137 | -------------------------------------------------------------------------------- /loader/popular_list.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type PopularList struct { 13 | ChainName string 14 | PopularWeight map[string]int32 15 | } 16 | 17 | type PopularListManager struct { 18 | chainToPopularList map[string]PopularList 19 | 20 | db *sql.DB 21 | alerter alert.Alerter 22 | mutex *sync.RWMutex 23 | } 24 | 25 | func NewPopularListManager(db *sql.DB, alerter alert.Alerter) *PopularListManager { 26 | return &PopularListManager{ 27 | chainToPopularList: make(map[string]PopularList), 28 | 29 | db: db, 30 | alerter: alerter, 31 | mutex: &sync.RWMutex{}, 32 | } 33 | } 34 | 35 | func (mgr *PopularListManager) GetPopularWeight(weights map[string]int32, chain string) bool { 36 | mgr.mutex.RLock() 37 | defer mgr.mutex.RUnlock() 38 | plInfo, ok := mgr.chainToPopularList[strings.ToLower(strings.TrimSpace(chain))] 39 | if ok { 40 | for t, w := range plInfo.PopularWeight { 41 | weights[t] = w 42 | } 43 | return ok 44 | } 45 | return false 46 | } 47 | 48 | func (mgr *PopularListManager) LoadAllPopularList() { 49 | // Query the database to select only id and name fields 50 | rows, err := mgr.db.Query("SELECT chain_name, popular_weight, tag FROM t_popular_list") 51 | 52 | if err != nil || rows == nil { 53 | mgr.alerter.AlertText("select t_popular_list error", err) 54 | return 55 | } 56 | 57 | defer rows.Close() 58 | 59 | chainToPopularList := make(map[string]PopularList) 60 | counter := 0 61 | 62 | // Iterate over the result set 63 | for rows.Next() { 64 | var weight int32 65 | var chainName, tag string 66 | 67 | if err := rows.Scan(&chainName, &weight, &tag); err != nil { 68 | mgr.alerter.AlertText("scan t_popular_list row error", err) 69 | } else { 70 | chainName = strings.ToLower(strings.TrimSpace(chainName)) 71 | 72 | var popularList PopularList 73 | popularList.PopularWeight = make(map[string]int32) 74 | if pl, ok := chainToPopularList[chainName]; ok { 75 | popularList = pl 76 | } 77 | popularList.ChainName = chainName 78 | popularList.PopularWeight[strings.TrimSpace(tag)] = weight 79 | 80 | chainToPopularList[chainName] = popularList 81 | 82 | counter++ 83 | } 84 | } 85 | 86 | // Check for errors from iterating over rows 87 | if err := rows.Err(); err != nil { 88 | mgr.alerter.AlertText("get next t_popular_list row error", err) 89 | return 90 | } 91 | 92 | mgr.mutex.Lock() 93 | mgr.chainToPopularList = chainToPopularList 94 | mgr.mutex.Unlock() 95 | log.Println("load all popular list: ", counter) 96 | 97 | } 98 | -------------------------------------------------------------------------------- /loader/src_tx.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "strings" 6 | 7 | "github.com/owlto-finance/utils-go/alert" 8 | ) 9 | 10 | type SrcTx struct { 11 | ChainId int32 12 | TxHash string 13 | Sender string 14 | Receiver string 15 | TargetAddress sql.NullString 16 | Token string 17 | Value string 18 | DstChainid sql.NullInt32 19 | IsTestnet sql.NullInt32 20 | TxTimestamp int32 21 | SrcTokenName sql.NullString 22 | SrcTokenDecimal int32 23 | IsCctp int32 24 | SrcNonce int32 25 | ThirdpartyChannel int32 26 | ToExchange int32 27 | } 28 | 29 | type SrcTxManager struct { 30 | db *sql.DB 31 | alerter alert.Alerter 32 | //mutex *sync.RWMutex 33 | } 34 | 35 | func NewSrcTxManager(db *sql.DB, alerter alert.Alerter) *SrcTxManager { 36 | return &SrcTxManager{ 37 | db: db, 38 | alerter: alerter, 39 | //mutex: &sync.RWMutex{}, 40 | } 41 | } 42 | 43 | func (mgr *SrcTxManager) IsSrcTxExist(chainId int32, txHash string) bool { 44 | var id int64 45 | err := mgr.db.QueryRow("SELECT id FROM t_src_transaction where chainid = ? and tx_hash = ? ", chainId, strings.TrimSpace(txHash)).Scan(&id) 46 | return err == nil 47 | } 48 | 49 | func (mgr *SrcTxManager) SetResult(txHash string, isInvalid int32, isVerified int32) error { 50 | _, err := mgr.db.Exec("update t_src_transaction set is_invalid = ?, is_verified = ? where tx_hash = ? ", isInvalid, isVerified, txHash) 51 | if err != nil { 52 | mgr.alerter.AlertText("update t_transfer is_invalid error :", err) 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | func (mgr *SrcTxManager) Save(tx *SrcTx) error { 59 | tx.TxHash = strings.TrimSpace(tx.TxHash) 60 | tx.Sender = strings.TrimSpace(tx.Sender) 61 | tx.Receiver = strings.TrimSpace(tx.Receiver) 62 | tx.Token = strings.TrimSpace(tx.Token) 63 | tx.Value = strings.TrimSpace(tx.Value) 64 | tx.TargetAddress.String = strings.TrimSpace(tx.TargetAddress.String) 65 | tx.SrcTokenName.String = strings.TrimSpace(tx.SrcTokenName.String) 66 | 67 | query := `INSERT IGNORE INTO t_src_transaction (chainid, tx_hash, sender, receiver, target_address, token, value, dst_chainid, is_testnet, tx_timestamp, src_token_name, src_token_decimal, is_cctp, src_nonce, thirdparty_channel, to_exchange) 68 | VALUES (?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?)` 69 | 70 | // Execute the SQL statement with tx data 71 | _, err := mgr.db.Exec(query, tx.ChainId, tx.TxHash, tx.Sender, tx.Receiver, tx.TargetAddress, tx.Token, tx.Value, tx.DstChainid, tx.IsTestnet, tx.TxTimestamp, tx.SrcTokenName, tx.SrcTokenDecimal, tx.IsCctp, tx.SrcNonce, tx.ThirdpartyChannel, tx.ToExchange) 72 | if err != nil { 73 | mgr.alerter.AlertText("failed to insert src transaction", err) 74 | return err 75 | } 76 | 77 | return nil 78 | 79 | } 80 | -------------------------------------------------------------------------------- /loader/token_info.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type TokenInfo struct { 13 | TokenName string 14 | ChainName string 15 | TokenAddress string 16 | Decimals int32 17 | } 18 | 19 | type TokenInfoManager struct { 20 | chainNameTokenAddrs map[string]map[string]*TokenInfo 21 | chainNameTokenNames map[string]map[string]*TokenInfo 22 | db *sql.DB 23 | alerter alert.Alerter 24 | mutex *sync.RWMutex 25 | } 26 | 27 | func NewTokenInfoManager(db *sql.DB, alerter alert.Alerter) *TokenInfoManager { 28 | return &TokenInfoManager{ 29 | chainNameTokenAddrs: make(map[string]map[string]*TokenInfo), 30 | chainNameTokenNames: make(map[string]map[string]*TokenInfo), 31 | db: db, 32 | alerter: alerter, 33 | mutex: &sync.RWMutex{}, 34 | } 35 | } 36 | 37 | func (mgr *TokenInfoManager) GetByChainNameTokenAddr(chainName string, tokenAddr string) (*TokenInfo, bool) { 38 | mgr.mutex.RLock() 39 | defer mgr.mutex.RUnlock() 40 | tokenAddrs, ok := mgr.chainNameTokenAddrs[strings.ToLower(strings.TrimSpace(chainName))] 41 | if ok { 42 | token, ok := tokenAddrs[strings.ToLower(strings.TrimSpace(tokenAddr))] 43 | return token, ok 44 | } 45 | return nil, false 46 | } 47 | 48 | func (mgr *TokenInfoManager) AddToken(chainName string, tokenName string, tokenAddr string, decimals int32) { 49 | mgr.mutex.Lock() 50 | defer mgr.mutex.Unlock() 51 | var token TokenInfo 52 | token.ChainName = strings.TrimSpace(chainName) 53 | token.TokenAddress = strings.TrimSpace(tokenAddr) 54 | token.TokenName = strings.TrimSpace(tokenName) 55 | token.Decimals = decimals 56 | 57 | tokenAddrs, ok := mgr.chainNameTokenAddrs[strings.ToLower(token.ChainName)] 58 | if !ok { 59 | tokenAddrs = make(map[string]*TokenInfo) 60 | mgr.chainNameTokenAddrs[strings.ToLower(token.ChainName)] = tokenAddrs 61 | } 62 | tokenAddrs[strings.ToLower(token.TokenAddress)] = &token 63 | 64 | tokenNames, ok := mgr.chainNameTokenNames[strings.ToLower(token.ChainName)] 65 | if !ok { 66 | tokenNames = make(map[string]*TokenInfo) 67 | mgr.chainNameTokenNames[strings.ToLower(token.ChainName)] = tokenNames 68 | } 69 | tokenNames[strings.ToLower(token.TokenName)] = &token 70 | } 71 | 72 | func (mgr *TokenInfoManager) GetByChainNameTokenName(chainName string, tokenName string) (*TokenInfo, bool) { 73 | mgr.mutex.RLock() 74 | defer mgr.mutex.RUnlock() 75 | tokenNames, ok := mgr.chainNameTokenNames[strings.ToLower(strings.TrimSpace(chainName))] 76 | if ok { 77 | token, ok := tokenNames[strings.ToLower(strings.TrimSpace(tokenName))] 78 | return token, ok 79 | } 80 | return nil, false 81 | } 82 | 83 | func (mgr *TokenInfoManager) GetTokenAddresses(chainName string) []string { 84 | addrs := make([]string, 0) 85 | mgr.mutex.RLock() 86 | tokenAddrs, ok := mgr.chainNameTokenAddrs[strings.ToLower(strings.TrimSpace(chainName))] 87 | if ok { 88 | for _, token := range tokenAddrs { 89 | addrs = append(addrs, token.TokenAddress) 90 | } 91 | } 92 | mgr.mutex.RUnlock() 93 | return addrs 94 | } 95 | 96 | func (mgr *TokenInfoManager) MergeNativeTokens(chainManager ChainInfoManager) { 97 | allIDs := chainManager.GetChainInfoIds() 98 | 99 | mgr.mutex.Lock() 100 | defer mgr.mutex.Unlock() 101 | for _, id := range allIDs { 102 | chainInfo, ok := chainManager.GetChainInfoById(id) 103 | if !ok { 104 | continue 105 | } 106 | var token TokenInfo 107 | token.ChainName = chainInfo.Name 108 | token.TokenAddress = "0x0000000000000000000000000000000000000000" 109 | token.TokenName = chainInfo.GasTokenName 110 | token.Decimals = chainInfo.GasTokenDecimal 111 | 112 | tokenAddrs, ok := mgr.chainNameTokenAddrs[strings.ToLower(token.ChainName)] 113 | if !ok { 114 | tokenAddrs = make(map[string]*TokenInfo) 115 | mgr.chainNameTokenAddrs[strings.ToLower(token.ChainName)] = tokenAddrs 116 | } 117 | tokenNames, ok := mgr.chainNameTokenNames[strings.ToLower(token.ChainName)] 118 | if !ok { 119 | tokenNames = make(map[string]*TokenInfo) 120 | mgr.chainNameTokenNames[strings.ToLower(token.ChainName)] = tokenNames 121 | } 122 | _, ok = mgr.chainNameTokenNames[strings.ToLower(token.ChainName)][strings.ToLower(token.TokenName)] 123 | if !ok { 124 | tokenNames[strings.ToLower(token.TokenName)] = &token 125 | tokenAddrs[strings.ToLower(token.TokenAddress)] = &token 126 | } 127 | 128 | } 129 | 130 | } 131 | 132 | func (mgr *TokenInfoManager) LoadAllToken() { 133 | // Query the database to select only id and name fields 134 | rows, err := mgr.db.Query("SELECT token_name, chain_name, token_address, decimals FROM t_token_info") 135 | 136 | if err != nil || rows == nil { 137 | mgr.alerter.AlertText("select t_token_info error", err) 138 | return 139 | } 140 | 141 | defer rows.Close() 142 | 143 | chainNameTokenAddrs := make(map[string]map[string]*TokenInfo) 144 | chainNameTokenNames := make(map[string]map[string]*TokenInfo) 145 | counter := 0 146 | 147 | // Iterate over the result set 148 | for rows.Next() { 149 | var token TokenInfo 150 | 151 | if err := rows.Scan(&token.TokenName, &token.ChainName, &token.TokenAddress, &token.Decimals); err != nil { 152 | mgr.alerter.AlertText("scan t_token_info row error", err) 153 | } else { 154 | token.ChainName = strings.TrimSpace(token.ChainName) 155 | token.TokenAddress = strings.TrimSpace(token.TokenAddress) 156 | token.TokenName = strings.TrimSpace(token.TokenName) 157 | 158 | tokenAddrs, ok := chainNameTokenAddrs[strings.ToLower(token.ChainName)] 159 | if !ok { 160 | tokenAddrs = make(map[string]*TokenInfo) 161 | chainNameTokenAddrs[strings.ToLower(token.ChainName)] = tokenAddrs 162 | } 163 | tokenAddrs[strings.ToLower(token.TokenAddress)] = &token 164 | 165 | tokenNames, ok := chainNameTokenNames[strings.ToLower(token.ChainName)] 166 | if !ok { 167 | tokenNames = make(map[string]*TokenInfo) 168 | chainNameTokenNames[strings.ToLower(token.ChainName)] = tokenNames 169 | } 170 | tokenNames[strings.ToLower(token.TokenName)] = &token 171 | 172 | counter++ 173 | } 174 | } 175 | 176 | // Check for errors from iterating over rows 177 | if err := rows.Err(); err != nil { 178 | mgr.alerter.AlertText("get next t_token_info row error", err) 179 | return 180 | } 181 | 182 | mgr.mutex.Lock() 183 | mgr.chainNameTokenAddrs = chainNameTokenAddrs 184 | mgr.chainNameTokenNames = chainNameTokenNames 185 | mgr.mutex.Unlock() 186 | log.Println("load all token info: ", counter) 187 | 188 | } 189 | -------------------------------------------------------------------------------- /loader/update_price.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/owlto-finance/utils-go/alert" 10 | ) 11 | 12 | type UpdatePrice struct { 13 | TokenName string 14 | Price string 15 | UpdateTimestamp string 16 | } 17 | 18 | type UpdatePriceManager struct { 19 | tokens map[string]*UpdatePrice 20 | 21 | db *sql.DB 22 | alerter alert.Alerter 23 | mutex *sync.RWMutex 24 | } 25 | 26 | func NewUpdatePriceManager(db *sql.DB, alerter alert.Alerter) *UpdatePriceManager { 27 | return &UpdatePriceManager{ 28 | tokens: make(map[string]*UpdatePrice), 29 | 30 | db: db, 31 | alerter: alerter, 32 | mutex: &sync.RWMutex{}, 33 | } 34 | } 35 | 36 | func (mgr *UpdatePriceManager) GetUpdatePrice(tokenName string) (*UpdatePrice, bool) { 37 | mgr.mutex.RLock() 38 | defer mgr.mutex.RUnlock() 39 | 40 | info, ok := mgr.tokens[strings.ToLower(strings.TrimSpace(tokenName))] 41 | return info, ok 42 | } 43 | 44 | func (mgr *UpdatePriceManager) LoadAllPrice() { 45 | // Query the database to select only id and name fields 46 | rows, err := mgr.db.Query("SELECT token, price, update_timestamp FROM t_update_price") 47 | if err != nil || rows == nil { 48 | mgr.alerter.AlertText("select t_update error", err) 49 | return 50 | } 51 | defer rows.Close() 52 | 53 | tokens := make(map[string]*UpdatePrice) 54 | counter := 0 55 | 56 | for rows.Next() { 57 | var prices UpdatePrice 58 | 59 | if err := rows.Scan(&prices.TokenName, &prices.Price, &prices.UpdateTimestamp); err != nil { 60 | mgr.alerter.AlertText("scan t_update_price row error", err) 61 | } else { 62 | prices.TokenName = strings.TrimSpace(prices.TokenName) 63 | prices.Price = strings.TrimSpace(prices.Price) 64 | prices.UpdateTimestamp = strings.TrimSpace(prices.UpdateTimestamp) 65 | tokens[strings.ToLower(prices.TokenName)] = &prices 66 | counter++ 67 | } 68 | } 69 | 70 | // Check for errors from iterating over rows 71 | if err := rows.Err(); err != nil { 72 | mgr.alerter.AlertText("get next t_update_price row error", err) 73 | return 74 | } 75 | 76 | mgr.mutex.Lock() 77 | mgr.tokens = tokens 78 | mgr.mutex.Unlock() 79 | log.Println("load all update price: ", counter) 80 | } 81 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | "time" 12 | 13 | "github.com/sirupsen/logrus" 14 | "gopkg.in/natefinch/lumberjack.v2" 15 | ) 16 | 17 | var log *logrus.Logger 18 | 19 | func init() { 20 | log = logrus.New() 21 | 22 | // Get project name 23 | projectName := getProjectName() 24 | 25 | // Get user's home directory 26 | homeDir, err := os.UserHomeDir() 27 | if err != nil { 28 | panic("Failed to get user home directory: " + err.Error()) 29 | } 30 | 31 | // Create project log directory 32 | logDir := filepath.Join(homeDir, "logs", projectName) 33 | if err = os.MkdirAll(logDir, os.ModePerm); err != nil { 34 | panic("Failed to create log directory: " + err.Error()) 35 | } 36 | 37 | logFilePath := filepath.Join(logDir, "app.log") 38 | 39 | // Configure Lumberjack 40 | lumberjackLogger := &lumberjack.Logger{ 41 | Filename: logFilePath, 42 | MaxSize: 100, // Max size in MB 43 | MaxBackups: 3, 44 | MaxAge: 28, // Max age in days 45 | } 46 | 47 | // Use custom formatter 48 | customFormatter := &CustomFormatter{EnableColors: true} 49 | 50 | // Set log output to console with colors 51 | log.SetOutput(os.Stdout) 52 | log.SetFormatter(customFormatter) 53 | log.SetLevel(logrus.InfoLevel) 54 | 55 | // Set timezone to Beijing time 56 | log.AddHook(&TimezoneHook{Location: "Asia/Shanghai"}) 57 | 58 | // Add file output hook with custom formatter 59 | fileFormatter := &CustomFormatter{EnableColors: false} 60 | log.AddHook(&Hook{Writer: lumberjackLogger, Formatter: fileFormatter, LogLevels: logrus.AllLevels}) 61 | } 62 | 63 | func getProjectName() string { 64 | // Get executable path 65 | exePath, err := os.Executable() 66 | if err != nil { 67 | panic("Failed to get executable path: " + err.Error()) 68 | } 69 | 70 | // Extract project name from executable path 71 | projectName := filepath.Base(exePath) 72 | if runtime.GOOS == "windows" { 73 | projectName = strings.TrimSuffix(projectName, ".exe") 74 | } 75 | return projectName 76 | } 77 | 78 | type Hook struct { 79 | Writer io.Writer 80 | Formatter logrus.Formatter 81 | LogLevels []logrus.Level 82 | } 83 | 84 | func (hook *Hook) Levels() []logrus.Level { 85 | return hook.LogLevels 86 | } 87 | 88 | func (hook *Hook) Fire(entry *logrus.Entry) error { 89 | line, err := hook.Formatter.Format(entry) 90 | if err != nil { 91 | return err 92 | } 93 | _, err = hook.Writer.Write(line) 94 | return err 95 | } 96 | 97 | type TimezoneHook struct { 98 | Location string 99 | } 100 | 101 | func (hook *TimezoneHook) Levels() []logrus.Level { 102 | return logrus.AllLevels 103 | } 104 | 105 | func (hook *TimezoneHook) Fire(entry *logrus.Entry) error { 106 | loc, err := time.LoadLocation(hook.Location) 107 | if err != nil { 108 | return err 109 | } 110 | entry.Time = entry.Time.In(loc) 111 | return nil 112 | } 113 | 114 | type CustomFormatter struct { 115 | EnableColors bool 116 | } 117 | 118 | func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) { 119 | // Get caller file and line number 120 | file, line := findCaller() 121 | file = filepath.Base(file) 122 | 123 | // Format timestamp, filename, line number, and log level 124 | timestamp := entry.Time.Format("2006-01-02 15:04:05.000000") 125 | level := strings.ToUpper(entry.Level.String()) 126 | if level == "WARNING" { 127 | level = "WARN" 128 | } 129 | 130 | logId := entry.Data["logId"] 131 | logIdStr := "" 132 | if logId != nil && logId != "unknown" { 133 | logIdStr = fmt.Sprintf("%v ", logId) 134 | } 135 | 136 | if f.EnableColors { 137 | levelColor := getColorByLevel(entry.Level) 138 | msg := fmt.Sprintf("%s %s:%d %s[%s%s%s] %s\n", timestamp, file, line, logIdStr, levelColor, level, "\033[0m", entry.Message) 139 | return []byte(msg), nil 140 | } 141 | 142 | msg := fmt.Sprintf("%s %s:%d %s[%s] %s\n", timestamp, file, line, logIdStr, level, entry.Message) 143 | return []byte(msg), nil 144 | } 145 | 146 | func getColorByLevel(level logrus.Level) string { 147 | switch level { 148 | case logrus.DebugLevel: 149 | return "\033[37m" // White 150 | case logrus.InfoLevel: 151 | return "\033[32m" // Green 152 | case logrus.WarnLevel: 153 | return "\033[33m" // Yellow 154 | case logrus.ErrorLevel: 155 | return "\033[31m" // Red 156 | case logrus.FatalLevel, logrus.PanicLevel: 157 | return "\033[35m" // Purple 158 | default: 159 | return "\033[0m" // Default 160 | } 161 | } 162 | 163 | func findCaller() (string, int) { 164 | for skip := 7; ; skip++ { 165 | pc, file, line, ok := runtime.Caller(skip) 166 | if !ok { 167 | return "unknown", 0 168 | } 169 | funcName := runtime.FuncForPC(pc).Name() 170 | if !strings.Contains(funcName, "log.") && !strings.Contains(funcName, "logrus") { 171 | return file, line 172 | } 173 | } 174 | } 175 | 176 | func CtxWithFields(ctx context.Context) *logrus.Entry { 177 | requestId := ctx.Value("logId") 178 | if requestId == nil { 179 | requestId = "unknown" 180 | } 181 | return log.WithField("logId", requestId) 182 | } 183 | 184 | // Wrappers for logrus functions 185 | func Info(args ...interface{}) { 186 | log.Info(args...) 187 | } 188 | 189 | func Infof(format string, args ...interface{}) { 190 | log.Infof(format, args...) 191 | } 192 | 193 | func Error(args ...interface{}) { 194 | log.Error(args...) 195 | } 196 | 197 | func Errorf(format string, args ...interface{}) { 198 | log.Errorf(format, args...) 199 | } 200 | 201 | func Debug(args ...interface{}) { 202 | log.Debug(args...) 203 | } 204 | 205 | func Debugf(format string, args ...interface{}) { 206 | log.Debugf(format, args...) 207 | } 208 | 209 | func Warn(args ...interface{}) { 210 | log.Warn(args...) 211 | } 212 | 213 | func Warnf(format string, args ...interface{}) { 214 | log.Warnf(format, args...) 215 | } 216 | 217 | func Fatal(args ...interface{}) { 218 | log.Fatal(args...) 219 | } 220 | 221 | func Fatalf(format string, args ...interface{}) { 222 | log.Fatalf(format, args...) 223 | } 224 | 225 | func Panic(args ...interface{}) { 226 | log.Panic(args...) 227 | } 228 | 229 | func Panicf(format string, args ...interface{}) { 230 | log.Panicf(format, args...) 231 | } 232 | 233 | // CtxInfo logs an info message with context fields 234 | func CtxInfo(ctx context.Context, args ...interface{}) { 235 | CtxWithFields(ctx).Info(args...) 236 | } 237 | 238 | // CtxInfof logs a formatted info message with context fields 239 | func CtxInfof(ctx context.Context, format string, args ...interface{}) { 240 | CtxWithFields(ctx).Infof(format, args...) 241 | } 242 | 243 | // CtxError logs an error message with context fields 244 | func CtxError(ctx context.Context, args ...interface{}) { 245 | CtxWithFields(ctx).Error(args...) 246 | } 247 | 248 | // CtxErrorf logs a formatted error message with context fields 249 | func CtxErrorf(ctx context.Context, format string, args ...interface{}) { 250 | CtxWithFields(ctx).Errorf(format, args...) 251 | } 252 | 253 | // CtxDebug logs a debug message with context fields 254 | func CtxDebug(ctx context.Context, args ...interface{}) { 255 | CtxWithFields(ctx).Debug(args...) 256 | } 257 | 258 | // CtxDebugf logs a formatted debug message with context fields 259 | func CtxDebugf(ctx context.Context, format string, args ...interface{}) { 260 | CtxWithFields(ctx).Debugf(format, args...) 261 | } 262 | 263 | // CtxWarn logs a warning message with context fields 264 | func CtxWarn(ctx context.Context, args ...interface{}) { 265 | CtxWithFields(ctx).Warn(args...) 266 | } 267 | 268 | // CtxWarnf logs a formatted warning message with context fields 269 | func CtxWarnf(ctx context.Context, format string, args ...interface{}) { 270 | CtxWithFields(ctx).Warnf(format, args...) 271 | } 272 | -------------------------------------------------------------------------------- /network/http.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | func Request(url string, data interface{}, result interface{}) error { 12 | 13 | // Create a new HTTP client 14 | client := http.Client{} 15 | var method = "GET" 16 | var dataBytes []byte 17 | if data != nil { 18 | method = "POST" 19 | var err error 20 | dataBytes, err = json.Marshal(data) 21 | if err != nil { 22 | return fmt.Errorf("failed to marshal request body %v : %v", url, err) 23 | } 24 | } 25 | 26 | // Create a new HTTP request 27 | req, err := http.NewRequest(method, url, bytes.NewReader(dataBytes)) 28 | if err != nil { 29 | return fmt.Errorf("error creating request %v : %v", url, err) 30 | } 31 | 32 | // Set request headers 33 | req.Header.Set("Content-Type", "application/json") 34 | req.Header.Set("Accept", "application/json") 35 | 36 | // Send the HTTP request 37 | resp, err := client.Do(req) 38 | if err != nil { 39 | return fmt.Errorf("error sending request %v : %v", url, err) 40 | } 41 | defer resp.Body.Close() 42 | 43 | // Check response status code 44 | if resp.StatusCode != http.StatusOK { 45 | return fmt.Errorf("unexpected status code %v : %v", url, resp.StatusCode) 46 | } 47 | 48 | body, err := io.ReadAll(resp.Body) 49 | if err != nil { 50 | return fmt.Errorf("error read body %v : %v - %v", url, err, body) 51 | } 52 | 53 | err = json.Unmarshal(body, &result) 54 | if err != nil { 55 | return fmt.Errorf("error read body %v : %v - %v", url, err, body) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /pointer/pointer.go: -------------------------------------------------------------------------------- 1 | package pointer 2 | 3 | func Ptr[T any](v T) *T { 4 | return &v 5 | } 6 | 7 | func GetValue[T any](ptr *T) T { 8 | if ptr == nil { 9 | var zero T 10 | return zero 11 | } 12 | return *ptr 13 | } 14 | -------------------------------------------------------------------------------- /rpc/bitcoin.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | _ "github.com/gagliardetto/solana-go" 10 | "github.com/owlto-finance/utils-go/loader" 11 | "github.com/owlto-finance/utils-go/network" 12 | "github.com/owlto-finance/utils-go/util" 13 | ) 14 | 15 | type BitcoinRpc struct { 16 | chainInfo *loader.ChainInfo 17 | } 18 | 19 | func NewBitcoinRpc(chainInfo *loader.ChainInfo) *BitcoinRpc { 20 | return &BitcoinRpc{ 21 | chainInfo: chainInfo, 22 | } 23 | } 24 | 25 | func (w *BitcoinRpc) GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) { 26 | return "", 0, fmt.Errorf("no impl") 27 | } 28 | 29 | func (w *BitcoinRpc) GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) { 30 | ownerAddr = strings.TrimSpace(ownerAddr) 31 | tokenAddr = strings.TrimSpace(tokenAddr) 32 | 33 | if util.IsHexStringZero(tokenAddr) { 34 | var data map[string]interface{} 35 | request := map[string]interface{}{ 36 | "method": "bb_getaddress", 37 | "params": []any{ownerAddr, map[string]interface{}{ 38 | "page": 1, 39 | "size": 1, 40 | "fromHeight": 0, 41 | "details": "basic", 42 | }}, 43 | } 44 | err := network.Request(w.chainInfo.RpcEndPoint, request, &data) 45 | if err != nil { 46 | return big.NewInt(0), err 47 | } 48 | 49 | if result, ok := data["result"].(map[string]interface{}); ok { 50 | if balance, ok := result["balance"].(string); ok { 51 | value, ok := big.NewInt(0).SetString(balance, 10) 52 | if ok { 53 | return value, nil 54 | } 55 | } 56 | } 57 | return big.NewInt(0), nil 58 | } else { 59 | return big.NewInt(0), fmt.Errorf("not impl") 60 | } 61 | } 62 | 63 | func (w *BitcoinRpc) GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) { 64 | return big.NewInt(0), fmt.Errorf("not impl") 65 | } 66 | 67 | func (w *BitcoinRpc) IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) { 68 | return false, 0, fmt.Errorf("not impl") 69 | } 70 | 71 | func (w *BitcoinRpc) Client() interface{} { 72 | return w.chainInfo.Client 73 | } 74 | 75 | func (w *BitcoinRpc) Backend() int32 { 76 | return 4 77 | } 78 | 79 | func (w *BitcoinRpc) GetLatestBlockNumber(ctx context.Context) (int64, error) { 80 | return 0, fmt.Errorf("not impl") 81 | } 82 | -------------------------------------------------------------------------------- /rpc/evm.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | ethtypes "github.com/ethereum/go-ethereum/core/types" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | "github.com/owlto-finance/utils-go/abi/erc20" 13 | "github.com/owlto-finance/utils-go/loader" 14 | "github.com/owlto-finance/utils-go/log" 15 | "github.com/owlto-finance/utils-go/util" 16 | ) 17 | 18 | type EvmRpc struct { 19 | chainInfo *loader.ChainInfo 20 | } 21 | 22 | func NewEvmRpc(chainInfo *loader.ChainInfo) *EvmRpc { 23 | return &EvmRpc{ 24 | chainInfo: chainInfo, 25 | } 26 | } 27 | 28 | func (w *EvmRpc) GetClient() *ethclient.Client { 29 | return w.chainInfo.Client.(*ethclient.Client) 30 | } 31 | 32 | func (w *EvmRpc) Client() interface{} { 33 | return w.chainInfo.Client 34 | } 35 | 36 | func (w *EvmRpc) Backend() int32 { 37 | return 1 38 | } 39 | 40 | func (w *EvmRpc) GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) { 41 | return "", 0, fmt.Errorf("no impl") 42 | } 43 | 44 | func (w *EvmRpc) GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) { 45 | econtract, err := erc20.NewErc20(common.HexToAddress(tokenAddr), w.GetClient()) 46 | if err != nil { 47 | return nil, err 48 | } 49 | allowance, err := econtract.Allowance(nil, common.HexToAddress(ownerAddr), common.HexToAddress(spenderAddr)) 50 | 51 | if err != nil { 52 | return nil, err 53 | } 54 | return allowance, nil 55 | } 56 | 57 | func (w *EvmRpc) GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) { 58 | ownerAddr = strings.TrimSpace(ownerAddr) 59 | tokenAddr = strings.TrimSpace(tokenAddr) 60 | 61 | if util.IsHexStringZero(tokenAddr) { 62 | nativeBalance, err := w.GetClient().BalanceAt(ctx, common.HexToAddress(ownerAddr), nil) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return nativeBalance, nil 67 | } else { 68 | econtract, err := erc20.NewErc20(common.HexToAddress(tokenAddr), w.GetClient()) 69 | if err != nil { 70 | return nil, err 71 | } 72 | balance, err := econtract.BalanceOf(nil, common.HexToAddress(ownerAddr)) 73 | 74 | if err != nil { 75 | return nil, err 76 | } 77 | return balance, nil 78 | } 79 | } 80 | 81 | func (w *EvmRpc) IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) { 82 | receipt, err := w.GetClient().TransactionReceipt(ctx, common.HexToHash(hash)) 83 | if err != nil { 84 | return false, 0, err 85 | } 86 | if receipt == nil { 87 | return false, 0, fmt.Errorf("get receipt failed") 88 | } 89 | return receipt.Status == ethtypes.ReceiptStatusSuccessful, receipt.BlockNumber.Int64(), nil 90 | } 91 | 92 | func (w *EvmRpc) GetLatestBlockNumber(ctx context.Context) (int64, error) { 93 | blockNumber, err := w.GetClient().BlockNumber(ctx) 94 | if err != nil { 95 | log.Errorf("%v get latest block number error %v", w.chainInfo.Name, err) 96 | return 0, err 97 | } 98 | return int64(blockNumber), nil 99 | } 100 | -------------------------------------------------------------------------------- /rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/owlto-finance/utils-go/loader" 9 | ) 10 | 11 | type Rpc interface { 12 | Client() interface{} 13 | Backend() int32 14 | GetLatestBlockNumber(ctx context.Context) (int64, error) 15 | IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) 16 | GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) 17 | GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) 18 | GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) 19 | } 20 | 21 | func GetRpc(chainInfo *loader.ChainInfo) (Rpc, error) { 22 | if chainInfo.Backend == 1 { 23 | return NewEvmRpc(chainInfo), nil 24 | } else if chainInfo.Backend == 2 { 25 | return NewStarknetRpc(chainInfo), nil 26 | } else if chainInfo.Backend == 3 { 27 | return NewSolanaRpc(chainInfo), nil 28 | } else if chainInfo.Backend == 4 { 29 | return NewBitcoinRpc(chainInfo), nil 30 | } else if chainInfo.Backend == 5 { 31 | return NewZksliteRpc(chainInfo), nil 32 | } 33 | return nil, fmt.Errorf("unsupport backend %v", chainInfo.Backend) 34 | } 35 | -------------------------------------------------------------------------------- /rpc/rpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gagliardetto/solana-go/rpc" 8 | "github.com/owlto-finance/utils-go/loader" 9 | ) 10 | 11 | func TestSol(t *testing.T) { 12 | t.Log("test sol...") 13 | solRpc := NewSolanaRpc(&loader.ChainInfo{Name: "SolanaMainnet", Client: rpc.New("https://api.mainnet-beta.solana.com")}) 14 | t.Log(solRpc.GetTokenInfo(context.TODO(), "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")) 15 | t.Log(solRpc.GetTokenInfo(context.TODO(), "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")) 16 | t.Log(solRpc.GetTokenInfo(context.TODO(), "J8qZijXxrypJin5Y27qcTvNjmd5ybF44NJdDKCSkXxWv")) 17 | t.Log(solRpc.GetTokenInfo(context.TODO(), "JC2iABaZUucksCEHZ95NCxb2BaBVhu8g4efaTNtyVsYL")) 18 | t.Log(solRpc.GetTokenInfo(context.TODO(), "J5tzd1ww1V1qrgDUQHVCGqpmpbnEnjzGs9LAqJxwkNde")) 19 | t.Log(solRpc.GetTokenInfo(context.TODO(), "zxTtD4MMnEAgHMvXmfgPCyMY61ivxX5zwu12hTSqLoA")) 20 | t.Log(solRpc.GetTokenInfo(context.TODO(), "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")) 21 | t.Log(solRpc.GetTokenInfo(context.TODO(), "J8qZijXxrypJin5Y27qcTvNjmd5ybF44NJdDKCSkXxWv")) 22 | t.Log(solRpc.GetTokenInfo(context.TODO(), "JC2iABaZUucksCEHZ95NCxb2BaBVhu8g4efaTNtyVsYL")) 23 | t.Log(solRpc.GetTokenInfo(context.TODO(), "J5tzd1ww1V1qrgDUQHVCGqpmpbnEnjzGs9LAqJxwkNde")) 24 | t.Log(solRpc.GetTokenInfo(context.TODO(), "zxTtD4MMnEAgHMvXmfgPCyMY61ivxX5zwu12hTSqLoA")) 25 | t.Log(solRpc.GetTokenInfo(context.TODO(), "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")) 26 | t.Log(solRpc.GetTokenInfo(context.TODO(), "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /rpc/solana.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/blocto/solana-go-sdk/program/metaplex/token_metadata" 10 | bin "github.com/gagliardetto/binary" 11 | "github.com/gagliardetto/solana-go" 12 | "github.com/gagliardetto/solana-go/programs/token" 13 | "github.com/gagliardetto/solana-go/rpc" 14 | "github.com/owlto-finance/utils-go/loader" 15 | "github.com/owlto-finance/utils-go/log" 16 | sol "github.com/owlto-finance/utils-go/txn/solana" 17 | "github.com/owlto-finance/utils-go/util" 18 | ) 19 | 20 | type SolanaRpc struct { 21 | tokenInfoMgr *loader.TokenInfoManager 22 | chainInfo *loader.ChainInfo 23 | } 24 | 25 | func NewSolanaRpc(chainInfo *loader.ChainInfo) *SolanaRpc { 26 | return &SolanaRpc{ 27 | chainInfo: chainInfo, 28 | tokenInfoMgr: loader.NewTokenInfoManager(nil, nil), 29 | } 30 | } 31 | 32 | func (w *SolanaRpc) GetClient() *rpc.Client { 33 | return w.chainInfo.Client.(*rpc.Client) 34 | } 35 | 36 | func (w *SolanaRpc) GetAccount(ctx context.Context, ownerAddr string) (*rpc.Account, error) { 37 | ownerAddr = strings.TrimSpace(ownerAddr) 38 | 39 | ownerpk, err := solana.PublicKeyFromBase58(ownerAddr) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | rsp, err := w.GetClient().GetAccountInfo( 45 | ctx, 46 | ownerpk, 47 | ) 48 | 49 | if err != nil { 50 | return nil, err 51 | } else { 52 | return rsp.Value, nil 53 | } 54 | } 55 | 56 | func (w *SolanaRpc) GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) { 57 | if util.IsHexStringZero(tokenAddr) || tokenAddr == "11111111111111111111111111111111" { 58 | return "SOL", 9, nil 59 | } 60 | tokenInfo, ok := w.tokenInfoMgr.GetByChainNameTokenAddr(w.chainInfo.Name, tokenAddr) 61 | if ok { 62 | return tokenInfo.TokenName, tokenInfo.Decimals, nil 63 | } 64 | 65 | mintpk, err := solana.PublicKeyFromBase58(tokenAddr) 66 | if err != nil { 67 | return "", 0, err 68 | } 69 | 70 | metapk, _, err := solana.FindTokenMetadataAddress(mintpk) 71 | if err != nil { 72 | return "", 0, err 73 | } 74 | 75 | symbol := "UNKNOWN" 76 | rsp, err := w.GetClient().GetAccountInfo( 77 | ctx, 78 | metapk, 79 | ) 80 | if err == nil { 81 | meta, err := token_metadata.MetadataDeserialize(rsp.GetBinary()) 82 | if err != nil { 83 | return "", 0, err 84 | } 85 | symbol = meta.Data.Symbol 86 | } else if err != rpc.ErrNotFound { 87 | return "", 0, err 88 | } 89 | 90 | rsp, err = w.GetClient().GetAccountInfo( 91 | ctx, 92 | mintpk, 93 | ) 94 | if err != nil { 95 | return "", 0, err 96 | } 97 | var mintAccount token.Mint 98 | decoder := bin.NewBorshDecoder(rsp.GetBinary()) 99 | err = mintAccount.UnmarshalWithDecoder(decoder) 100 | if err != nil { 101 | return "", 0, err 102 | } 103 | 104 | w.tokenInfoMgr.AddToken(w.chainInfo.Name, symbol, tokenAddr, int32(mintAccount.Decimals)) 105 | return symbol, int32(mintAccount.Decimals), nil 106 | 107 | } 108 | 109 | func (w *SolanaRpc) GetSplAccount(ctx context.Context, ownerAddr string, tokenAddr string) (*token.Account, error) { 110 | ownerAddr = strings.TrimSpace(ownerAddr) 111 | tokenAddr = strings.TrimSpace(tokenAddr) 112 | 113 | ownerpk, err := solana.PublicKeyFromBase58(ownerAddr) 114 | if err != nil { 115 | return nil, err 116 | } 117 | mintpk, err := solana.PublicKeyFromBase58(tokenAddr) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | ownerAta, err := sol.GetAtaFromPk(ownerpk, mintpk) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | rsp, err := w.GetClient().GetAccountInfo( 128 | ctx, 129 | ownerAta, 130 | ) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | var tokenAccount token.Account 136 | decoder := bin.NewBorshDecoder(rsp.GetBinary()) 137 | err = tokenAccount.UnmarshalWithDecoder(decoder) 138 | if err != nil { 139 | return nil, err 140 | } else { 141 | return &tokenAccount, nil 142 | } 143 | } 144 | 145 | func (w *SolanaRpc) GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) { 146 | ownerAddr = strings.TrimSpace(ownerAddr) 147 | tokenAddr = strings.TrimSpace(tokenAddr) 148 | 149 | if util.IsHexStringZero(tokenAddr) || tokenAddr == "11111111111111111111111111111111" { 150 | account, err := w.GetAccount(ctx, ownerAddr) 151 | if err != nil { 152 | if err == rpc.ErrNotFound { 153 | return big.NewInt(0), nil 154 | } 155 | return nil, err 156 | } 157 | return big.NewInt(int64(account.Lamports)), nil 158 | } else { 159 | sqlAccount, err := w.GetSplAccount(ctx, ownerAddr, tokenAddr) 160 | if err != nil { 161 | if err == rpc.ErrNotFound { 162 | return big.NewInt(0), nil 163 | } 164 | return nil, err 165 | } 166 | return big.NewInt(int64(sqlAccount.Amount)), nil 167 | } 168 | } 169 | 170 | func (w *SolanaRpc) GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) { 171 | sqlAccount, err := w.GetSplAccount(ctx, ownerAddr, tokenAddr) 172 | if err != nil { 173 | if err == rpc.ErrNotFound { 174 | return big.NewInt(0), nil 175 | } 176 | return nil, err 177 | } else { 178 | return big.NewInt(int64(sqlAccount.DelegatedAmount)), nil 179 | } 180 | } 181 | 182 | func (w *SolanaRpc) IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) { 183 | sig, err := solana.SignatureFromBase58(hash) 184 | if err != nil { 185 | return false, 0, err 186 | } 187 | 188 | receipt, err := w.GetClient().GetTransaction(ctx, sig, nil) 189 | if err != nil { 190 | return false, 0, err 191 | } 192 | if receipt == nil { 193 | return false, 0, fmt.Errorf("get receipt failed") 194 | } 195 | return receipt.Meta.Err == nil, int64(receipt.Slot), nil 196 | } 197 | 198 | func (w *SolanaRpc) Client() interface{} { 199 | return w.chainInfo.Client 200 | } 201 | 202 | func (w *SolanaRpc) Backend() int32 { 203 | return 3 204 | } 205 | 206 | func (w *SolanaRpc) GetLatestBlockNumber(ctx context.Context) (int64, error) { 207 | blockNumber, err := w.GetClient().GetSlot( 208 | context.TODO(), 209 | rpc.CommitmentFinalized, 210 | ) 211 | 212 | if err != nil { 213 | log.Errorf("%v get latest block number error %v", w.chainInfo.Name, err) 214 | return 0, err 215 | } 216 | return int64(blockNumber), nil 217 | 218 | } 219 | -------------------------------------------------------------------------------- /rpc/starknet.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/NethermindEth/juno/core/felt" 10 | "github.com/NethermindEth/starknet.go/rpc" 11 | "github.com/NethermindEth/starknet.go/utils" 12 | "github.com/ethereum/go-ethereum/common/hexutil" 13 | "github.com/owlto-finance/utils-go/loader" 14 | "github.com/owlto-finance/utils-go/log" 15 | ) 16 | 17 | type StarknetRpc struct { 18 | chainInfo *loader.ChainInfo 19 | } 20 | 21 | func NewStarknetRpc(chainInfo *loader.ChainInfo) *StarknetRpc { 22 | return &StarknetRpc{ 23 | chainInfo: chainInfo, 24 | } 25 | } 26 | 27 | func (w *StarknetRpc) GetClient() *rpc.Provider { 28 | return w.chainInfo.Client.(*rpc.Provider) 29 | } 30 | 31 | func (w *StarknetRpc) Client() interface{} { 32 | return w.chainInfo.Client 33 | } 34 | 35 | func (w *StarknetRpc) GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) { 36 | return "", 0, fmt.Errorf("no impl") 37 | } 38 | 39 | func (w *StarknetRpc) GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) { 40 | ownerAddr = strings.TrimSpace(ownerAddr) 41 | tokenAddr = strings.TrimSpace(tokenAddr) 42 | 43 | token, err := utils.HexToFelt(tokenAddr) 44 | if err != nil { 45 | return nil, err 46 | } 47 | owner, err := utils.HexToFelt(ownerAddr) 48 | if err != nil { 49 | return nil, err 50 | } 51 | tx := rpc.FunctionCall{ 52 | ContractAddress: token, 53 | EntryPointSelector: utils.GetSelectorFromNameFelt("balanceOf"), 54 | Calldata: []*felt.Felt{owner}, 55 | } 56 | rsp, err := w.GetClient().Call(context.Background(), tx, rpc.BlockID{Tag: "latest"}) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if len(rsp) > 0 { 61 | return rsp[0].BigInt(new(big.Int)), nil 62 | } else { 63 | return big.NewInt(0), nil 64 | } 65 | } 66 | 67 | func (w *StarknetRpc) GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) { 68 | return nil, fmt.Errorf("starknet get allowance unsupport") 69 | } 70 | 71 | func (w *StarknetRpc) Backend() int32 { 72 | return 2 73 | } 74 | 75 | func (w *StarknetRpc) IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) { 76 | bhash, err := hexutil.Decode(hash) 77 | if err != nil { 78 | return false, 0, err 79 | } 80 | status, err := w.GetClient().GetTransactionStatus(ctx, new(felt.Felt).SetBytes(bhash)) 81 | if err != nil { 82 | return false, 0, err 83 | } 84 | if status == nil { 85 | return false, 0, fmt.Errorf("get status failed") 86 | } 87 | 88 | switch status.FinalityStatus { 89 | case rpc.TxnStatus_Rejected: 90 | return false, 0, nil 91 | case rpc.TxnStatus_Received: 92 | // ignore this status, wait for L2 confirmation 93 | return false, 0, fmt.Errorf("not complete: %v", status.FinalityStatus) 94 | case rpc.TxnStatus_Accepted_On_L2, rpc.TxnStatus_Accepted_On_L1: 95 | return true, 0, nil //status.ExecutionStatus == rpc.TxnExecutionStatusSUCCEEDED, nil 96 | default: 97 | return false, 0, fmt.Errorf("unknown tx status: %v", status.FinalityStatus) 98 | } 99 | } 100 | 101 | func (w *StarknetRpc) GetLatestBlockNumber(ctx context.Context) (int64, error) { 102 | blockNumber, err := w.GetClient().BlockNumber(ctx) 103 | if err != nil { 104 | log.Errorf("%v get latest block number error %v", w.chainInfo.Name, err) 105 | return 0, err 106 | } 107 | return int64(blockNumber), nil 108 | } 109 | -------------------------------------------------------------------------------- /rpc/zkslite.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/owlto-finance/utils-go/loader" 10 | "github.com/owlto-finance/utils-go/network" 11 | "github.com/owlto-finance/utils-go/util" 12 | ) 13 | 14 | type ZksliteRpc struct { 15 | chainInfo *loader.ChainInfo 16 | } 17 | 18 | func NewZksliteRpc(chainInfo *loader.ChainInfo) *ZksliteRpc { 19 | return &ZksliteRpc{ 20 | chainInfo: chainInfo, 21 | } 22 | } 23 | 24 | func (w *ZksliteRpc) Client() interface{} { 25 | return w.chainInfo.Client 26 | } 27 | 28 | func (w *ZksliteRpc) Backend() int32 { 29 | return 1 30 | } 31 | 32 | func (w *ZksliteRpc) GetTokenInfo(ctx context.Context, tokenAddr string) (string, int32, error) { 33 | return "", 0, fmt.Errorf("no impl") 34 | } 35 | 36 | func (w *ZksliteRpc) GetAllowance(ctx context.Context, ownerAddr string, tokenAddr string, spenderAddr string) (*big.Int, error) { 37 | return big.NewInt(0), fmt.Errorf("not impl") 38 | } 39 | 40 | func (w *ZksliteRpc) IsLastCharSlash(s string) bool { 41 | if len(s) == 0 { 42 | return false 43 | } 44 | return s[len(s)-1] == '/' 45 | } 46 | 47 | func (w *ZksliteRpc) GetBalance(ctx context.Context, ownerAddr string, tokenAddr string) (*big.Int, error) { 48 | ownerAddr = strings.TrimSpace(ownerAddr) 49 | tokenAddr = strings.TrimSpace(tokenAddr) 50 | 51 | var data map[string]interface{} 52 | request := map[string]interface{}{ 53 | "jsonrpc": "2.0", 54 | "id": 1, 55 | "method": "account_info", 56 | "params": []string{ownerAddr}, 57 | } 58 | 59 | var url = w.chainInfo.RpcEndPoint 60 | if w.IsLastCharSlash(url) { 61 | url += "jsrpc" 62 | } else { 63 | url += "/jsrpc" 64 | } 65 | 66 | if util.IsHexStringZero(tokenAddr) || tokenAddr == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" || tokenAddr == "0xdAC17F958D2ee523a2206206994597C13D831ec7" { 67 | err := network.Request(url, request, &data) 68 | if err != nil { 69 | return big.NewInt(0), err 70 | } 71 | 72 | if result, ok := data["result"].(map[string]interface{}); ok { 73 | if committed, ok := result["committed"].(map[string]interface{}); ok { 74 | if balances, ok := committed["balances"].(map[string]interface{}); ok { 75 | if util.IsHexStringZero(tokenAddr) { 76 | if eth, ok := balances["ETH"].(string); ok { 77 | value, ok := big.NewInt(0).SetString(eth, 10) 78 | if ok { 79 | return value, nil 80 | } 81 | } 82 | } else if tokenAddr == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" { 83 | if usdc, ok := balances["USDC"].(string); ok { 84 | value, ok := big.NewInt(0).SetString(usdc, 10) 85 | if ok { 86 | return value, nil 87 | } 88 | } 89 | } else if tokenAddr == "0xdAC17F958D2ee523a2206206994597C13D831ec7" { 90 | if usdt, ok := balances["USDT"].(string); ok { 91 | value, ok := big.NewInt(0).SetString(usdt, 10) 92 | if ok { 93 | return value, nil 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | return big.NewInt(0), nil 101 | } else { 102 | return big.NewInt(0), fmt.Errorf("not impl") 103 | } 104 | } 105 | 106 | func (w *ZksliteRpc) IsTxSuccess(ctx context.Context, hash string) (bool, int64, error) { 107 | return false, 0, fmt.Errorf("not impl") 108 | } 109 | 110 | func (w *ZksliteRpc) GetLatestBlockNumber(ctx context.Context) (int64, error) { 111 | return 0, fmt.Errorf("not impl") 112 | } 113 | -------------------------------------------------------------------------------- /system/dir.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func MakeDirAll(home string) error { 8 | _, err := os.Stat(home) 9 | if err == nil { 10 | return nil 11 | } else { 12 | if !os.IsNotExist(err) { 13 | return err 14 | } 15 | } 16 | 17 | if err := os.MkdirAll(home, 0755); err != nil { 18 | return err 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /system/signal.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "strconv" 7 | "syscall" 8 | ) 9 | 10 | type QuitCode struct { 11 | Code int 12 | } 13 | 14 | func (e QuitCode) Error() string { 15 | return strconv.Itoa(e.Code) 16 | } 17 | 18 | func WaitForQuitSignals() QuitCode { 19 | sigs := make(chan os.Signal, 1) 20 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 21 | sig := <-sigs 22 | return QuitCode{Code: int(sig.(syscall.Signal)) + 128} 23 | } 24 | -------------------------------------------------------------------------------- /task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "runtime/debug" 7 | "time" 8 | ) 9 | 10 | func RunTask(fn func()) { 11 | go func() { 12 | defer func() { 13 | if r := recover(); r != nil { 14 | log.Printf("Recovered from panic: %v, stack: %s", r, string(debug.Stack())) 15 | } 16 | }() 17 | fn() 18 | }() 19 | } 20 | 21 | func PeriodicTask(ctx context.Context, task func(), waitSecond time.Duration) { 22 | for { 23 | task() 24 | select { 25 | case <-ctx.Done(): 26 | return 27 | case <-time.After(waitSecond): 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /telemetry/metrics.go: -------------------------------------------------------------------------------- 1 | package telemetry 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/hashicorp/go-metrics" 11 | "github.com/hashicorp/go-metrics/datadog" 12 | metricsprom "github.com/hashicorp/go-metrics/prometheus" 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/prometheus/common/expfmt" 15 | ) 16 | 17 | // globalLabels defines the set of global labels that will be applied to all 18 | // metrics emitted using the telemetry package function wrappers. 19 | var globalLabels = []metrics.Label{} 20 | 21 | // Metrics supported format types. 22 | const ( 23 | FormatDefault = "" 24 | FormatPrometheus = "prometheus" 25 | FormatText = "text" 26 | 27 | MetricSinkInMem = "mem" 28 | MetricSinkStatsd = "statsd" 29 | MetricSinkDogsStatsd = "dogstatsd" 30 | ) 31 | 32 | // DisplayableSink is an interface that defines a method for displaying metrics. 33 | type DisplayableSink interface { 34 | DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error) 35 | } 36 | 37 | // Config defines the configuration options for application telemetry. 38 | type Config struct { 39 | // Prefixed with keys to separate services 40 | ServiceName string `mapstructure:"service-name"` 41 | 42 | // Enabled enables the application telemetry functionality. When enabled, 43 | // an in-memory sink is also enabled by default. Operators may also enabled 44 | // other sinks such as Prometheus. 45 | Enabled bool `mapstructure:"enabled"` 46 | 47 | // Enable prefixing gauge values with hostname 48 | EnableHostname bool `mapstructure:"enable-hostname"` 49 | 50 | // Enable adding hostname to labels 51 | EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` 52 | 53 | // Enable adding service to labels 54 | EnableServiceLabel bool `mapstructure:"enable-service-label"` 55 | 56 | // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. 57 | // It defines the retention duration in seconds. 58 | PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` 59 | 60 | // GlobalLabels defines a global set of name/value label tuples applied to all 61 | // metrics emitted using the wrapper functions defined in telemetry package. 62 | // 63 | // Example: 64 | // [["chain_id", "cosmoshub-1"]] 65 | GlobalLabels [][]string `mapstructure:"global-labels"` 66 | 67 | // MetricsSink defines the type of metrics backend to use. 68 | MetricsSink string `mapstructure:"metrics-sink" default:"mem"` 69 | 70 | // StatsdAddr defines the address of a statsd server to send metrics to. 71 | // Only utilized if MetricsSink is set to "statsd" or "dogstatsd". 72 | StatsdAddr string `mapstructure:"statsd-addr"` 73 | 74 | // DatadogHostname defines the hostname to use when emitting metrics to 75 | // Datadog. Only utilized if MetricsSink is set to "dogstatsd". 76 | DatadogHostname string `mapstructure:"datadog-hostname"` 77 | } 78 | 79 | // Metrics defines a wrapper around application telemetry functionality. It allows 80 | // metrics to be gathered at any point in time. When creating a Metrics object, 81 | // internally, a global metrics is registered with a set of sinks as configured 82 | // by the operator. In addition to the sinks, when a process gets a SIGUSR1, a 83 | // dump of formatted recent metrics will be sent to STDERR. 84 | type Metrics struct { 85 | sink metrics.MetricSink 86 | prometheusEnabled bool 87 | } 88 | 89 | // GatherResponse is the response type of registered metrics 90 | type GatherResponse struct { 91 | Metrics []byte 92 | ContentType string 93 | } 94 | 95 | // New creates a new instance of Metrics 96 | func New(cfg Config) (_ *Metrics, rerr error) { 97 | if !cfg.Enabled { 98 | return nil, nil 99 | } 100 | 101 | if numGlobalLabels := len(cfg.GlobalLabels); numGlobalLabels > 0 { 102 | parsedGlobalLabels := make([]metrics.Label, numGlobalLabels) 103 | for i, gl := range cfg.GlobalLabels { 104 | parsedGlobalLabels[i] = NewLabel(gl[0], gl[1]) 105 | } 106 | globalLabels = parsedGlobalLabels 107 | } 108 | 109 | metricsConf := metrics.DefaultConfig(cfg.ServiceName) 110 | metricsConf.EnableHostname = cfg.EnableHostname 111 | metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel 112 | 113 | var ( 114 | sink metrics.MetricSink 115 | err error 116 | ) 117 | switch cfg.MetricsSink { 118 | case MetricSinkStatsd: 119 | sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) 120 | case MetricSinkDogsStatsd: 121 | sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) 122 | default: 123 | memSink := metrics.NewInmemSink(10*time.Second, time.Minute) 124 | sink = memSink 125 | inMemSig := metrics.DefaultInmemSignal(memSink) 126 | defer func() { 127 | if rerr != nil { 128 | inMemSig.Stop() 129 | } 130 | }() 131 | } 132 | 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | m := &Metrics{sink: sink} 138 | fanout := metrics.FanoutSink{sink} 139 | 140 | if cfg.PrometheusRetentionTime > 0 { 141 | m.prometheusEnabled = true 142 | prometheusOpts := metricsprom.PrometheusOpts{ 143 | Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second, 144 | } 145 | 146 | promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | fanout = append(fanout, promSink) 152 | } 153 | 154 | if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil { 155 | return nil, err 156 | } 157 | 158 | return m, nil 159 | } 160 | 161 | // Gather collects all registered metrics and returns a GatherResponse where the 162 | // metrics are encoded depending on the type. Metrics are either encoded via 163 | // Prometheus or JSON if in-memory. 164 | func (m *Metrics) Gather(format string) (GatherResponse, error) { 165 | switch format { 166 | case FormatPrometheus: 167 | return m.gatherPrometheus() 168 | 169 | case FormatText: 170 | return m.gatherGeneric() 171 | 172 | case FormatDefault: 173 | return m.gatherGeneric() 174 | 175 | default: 176 | return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) 177 | } 178 | } 179 | 180 | // gatherPrometheus collects Prometheus metrics and returns a GatherResponse. 181 | // If Prometheus metrics are not enabled, it returns an error. 182 | func (m *Metrics) gatherPrometheus() (GatherResponse, error) { 183 | if !m.prometheusEnabled { 184 | return GatherResponse{}, fmt.Errorf("prometheus metrics are not enabled") 185 | } 186 | 187 | metricsFamilies, err := prometheus.DefaultGatherer.Gather() 188 | if err != nil { 189 | return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err) 190 | } 191 | 192 | buf := &bytes.Buffer{} 193 | defer buf.Reset() 194 | 195 | e := expfmt.NewEncoder(buf, expfmt.FmtText) 196 | 197 | for _, mf := range metricsFamilies { 198 | if err := e.Encode(mf); err != nil { 199 | return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err) 200 | } 201 | } 202 | 203 | return GatherResponse{ContentType: string(expfmt.FmtText), Metrics: buf.Bytes()}, nil 204 | } 205 | 206 | // gatherGeneric collects generic metrics and returns a GatherResponse. 207 | func (m *Metrics) gatherGeneric() (GatherResponse, error) { 208 | gm, ok := m.sink.(DisplayableSink) 209 | if !ok { 210 | return GatherResponse{}, fmt.Errorf("non in-memory metrics sink does not support generic format") 211 | } 212 | 213 | summary, err := gm.DisplayMetrics(nil, nil) 214 | if err != nil { 215 | return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err) 216 | } 217 | 218 | content, err := json.Marshal(summary) 219 | if err != nil { 220 | return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err) 221 | } 222 | 223 | return GatherResponse{ContentType: "application/json", Metrics: content}, nil 224 | } 225 | -------------------------------------------------------------------------------- /telemetry/wrapper.go: -------------------------------------------------------------------------------- 1 | package telemetry 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/hashicorp/go-metrics" 7 | ) 8 | 9 | // Common metric key constants 10 | const ( 11 | MetricKeyBeginBlocker = "begin_blocker" 12 | MetricKeyEndBlocker = "end_blocker" 13 | MetricKeyPrepareCheckStater = "prepare_check_stater" 14 | MetricKeyPrecommiter = "precommiter" 15 | MetricLabelNameModule = "module" 16 | ) 17 | 18 | // NewLabel creates a new instance of Label with name and value 19 | func NewLabel(name, value string) metrics.Label { 20 | return metrics.Label{Name: name, Value: value} 21 | } 22 | 23 | // ModuleMeasureSince provides a short hand method for emitting a time measure 24 | // metric for a module with a given set of keys. If any global labels are defined, 25 | // they will be added to the module label. 26 | func ModuleMeasureSince(module string, start time.Time, keys ...string) { 27 | metrics.MeasureSinceWithLabels( 28 | keys, 29 | start.UTC(), 30 | append([]metrics.Label{NewLabel(MetricLabelNameModule, module)}, globalLabels...), 31 | ) 32 | } 33 | 34 | // ModuleSetGauge provides a short hand method for emitting a gauge metric for a 35 | // module with a given set of keys. If any global labels are defined, they will 36 | // be added to the module label. 37 | func ModuleSetGauge(module string, val float32, keys ...string) { 38 | metrics.SetGaugeWithLabels( 39 | keys, 40 | val, 41 | append([]metrics.Label{NewLabel(MetricLabelNameModule, module)}, globalLabels...), 42 | ) 43 | } 44 | 45 | // IncrCounter provides a wrapper functionality for emitting a counter metric with 46 | // global labels (if any). 47 | func IncrCounter(val float32, keys ...string) { 48 | metrics.IncrCounterWithLabels(keys, val, globalLabels) 49 | } 50 | 51 | // IncrCounterWithLabels provides a wrapper functionality for emitting a counter 52 | // metric with global labels (if any) along with the provided labels. 53 | func IncrCounterWithLabels(keys []string, val float32, labels []metrics.Label) { 54 | metrics.IncrCounterWithLabels(keys, val, append(labels, globalLabels...)) 55 | } 56 | 57 | // SetGauge provides a wrapper functionality for emitting a gauge metric with 58 | // global labels (if any). 59 | func SetGauge(val float32, keys ...string) { 60 | metrics.SetGaugeWithLabels(keys, val, globalLabels) 61 | } 62 | 63 | // SetGaugeWithLabels provides a wrapper functionality for emitting a gauge 64 | // metric with global labels (if any) along with the provided labels. 65 | func SetGaugeWithLabels(keys []string, val float32, labels []metrics.Label) { 66 | metrics.SetGaugeWithLabels(keys, val, append(labels, globalLabels...)) 67 | } 68 | 69 | // MeasureSince provides a wrapper functionality for emitting a a time measure 70 | // metric with global labels (if any). 71 | func MeasureSince(start time.Time, keys ...string) { 72 | metrics.MeasureSinceWithLabels(keys, start.UTC(), globalLabels) 73 | } 74 | -------------------------------------------------------------------------------- /txn/evm/common.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "math/big" 9 | "strings" 10 | 11 | "github.com/ethereum/go-ethereum" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/ethereum/go-ethereum/ethclient" 14 | ) 15 | 16 | func EstimateGas(client *ethclient.Client, from string, to string, value *big.Int, data []byte) (uint64, error) { 17 | from = strings.TrimSpace(from) 18 | to = strings.TrimSpace(to) 19 | 20 | f := common.HexToAddress(from) 21 | t := common.HexToAddress(to) 22 | v := value 23 | if v == nil { 24 | v = big.NewInt(0) 25 | } 26 | if data == nil { 27 | data = []byte{} 28 | } 29 | cm := ethereum.CallMsg{ 30 | From: f, 31 | To: &t, 32 | Value: v, 33 | Data: data, 34 | } 35 | 36 | gas, err := client.EstimateGas(context.Background(), cm) 37 | gas = gas * 3 / 2 38 | return gas, err 39 | } 40 | 41 | func TransferBody(client *ethclient.Client, senderAddr string, receiverAddr string, amount *big.Int) ([]byte, error) { 42 | senderAddr = strings.TrimSpace(senderAddr) 43 | receiverAddr = strings.TrimSpace(receiverAddr) 44 | 45 | gas, err := EstimateGas(client, senderAddr, receiverAddr, amount, nil) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return ToBody(receiverAddr, amount, nil, gas) 50 | 51 | } 52 | 53 | func ToBody(to string, value *big.Int, input []byte, gas uint64) ([]byte, error) { 54 | to = strings.TrimSpace(to) 55 | t := common.HexToAddress(to) 56 | v := value 57 | if v == nil { 58 | v = big.NewInt(0) 59 | } 60 | m := map[string]interface{}{ 61 | "to": t.Hex(), 62 | "gas": fmt.Sprintf("0x%x", gas), 63 | "value": fmt.Sprintf("0x%x", v), 64 | } 65 | 66 | if input != nil { 67 | m["input"] = fmt.Sprintf("0x%s", hex.EncodeToString(input)) 68 | } 69 | 70 | // Marshal the map to a JSON string 71 | return json.Marshal(m) 72 | } 73 | -------------------------------------------------------------------------------- /txn/evm/erc20.go: -------------------------------------------------------------------------------- 1 | package evm 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/owlto-finance/utils-go/abi/erc20" 10 | ) 11 | 12 | func Erc20ApproveBody(client *ethclient.Client, senderAddr string, tokenAddr string, spender string, amount *big.Int) ([]byte, error) { 13 | senderAddr = strings.TrimSpace(senderAddr) 14 | tokenAddr = strings.TrimSpace(tokenAddr) 15 | spender = strings.TrimSpace(spender) 16 | 17 | abi, err := erc20.Erc20MetaData.GetAbi() 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | data, err := abi.Pack("approve", common.HexToAddress(spender), amount) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | gas, err := EstimateGas(client, senderAddr, tokenAddr, nil, data) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return ToBody(tokenAddr, nil, data, gas) 32 | 33 | // abi, _ := erc20.Erc20MetaData.GetAbi() 34 | 35 | // calldata, err := abi.Pack("approve", common.HexToAddress(spender), amount) 36 | // if err != nil { 37 | // return nil, err 38 | // } 39 | 40 | // return calldata, nil 41 | } 42 | 43 | func Erc20TransferBody(client *ethclient.Client, senderAddr string, tokenAddr string, receiverAddr string, amount *big.Int) ([]byte, error) { 44 | senderAddr = strings.TrimSpace(senderAddr) 45 | tokenAddr = strings.TrimSpace(tokenAddr) 46 | receiverAddr = strings.TrimSpace(receiverAddr) 47 | 48 | abi, err := erc20.Erc20MetaData.GetAbi() 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | data, err := abi.Pack("transfer", common.HexToAddress(receiverAddr), amount) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | gas, err := EstimateGas(client, senderAddr, tokenAddr, nil, data) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return ToBody(tokenAddr, nil, data, gas) 63 | } 64 | -------------------------------------------------------------------------------- /txn/solana/common.go: -------------------------------------------------------------------------------- 1 | package sol 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | "strings" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/gagliardetto/solana-go" 10 | "github.com/gagliardetto/solana-go/programs/system" 11 | ) 12 | 13 | type SolanaKeypair struct { 14 | PublicKey solana.PublicKey `json:"public_key"` 15 | PrivateKey solana.PrivateKey `json:"private_key"` 16 | } 17 | 18 | type SolanaAccount struct { 19 | PublicKey solana.PublicKey `json:"public_key"` 20 | IsWritable bool `json:"is_writable"` 21 | IsSigner bool `json:"is_signer"` 22 | } 23 | 24 | type SolanaInstruction struct { 25 | ProgramId solana.PublicKey `json:"program_id"` 26 | Accounts []SolanaAccount `json:"accounts"` 27 | Data hexutil.Bytes `json:"data"` 28 | } 29 | 30 | type SolanaBody struct { 31 | Instructions []SolanaInstruction `json:"instructions"` 32 | Keypairs []SolanaKeypair `json:"keypairs"` 33 | LookupTables map[solana.PublicKey]solana.PublicKeySlice `json:"lookup_tables"` 34 | } 35 | 36 | func (body *SolanaBody) AddInstructions(insts []solana.Instruction) error { 37 | for _, inst := range insts { 38 | data, err := inst.Data() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | maccs := make([]SolanaAccount, 0, len(inst.Accounts())) 44 | for _, acc := range inst.Accounts() { 45 | macc := SolanaAccount{ 46 | PublicKey: acc.PublicKey, 47 | IsWritable: acc.IsWritable, 48 | IsSigner: acc.IsSigner, 49 | } 50 | maccs = append(maccs, macc) 51 | } 52 | 53 | minst := SolanaInstruction{ 54 | ProgramId: inst.ProgramID(), 55 | Accounts: maccs, 56 | Data: data, 57 | } 58 | 59 | body.Instructions = append(body.Instructions, minst) 60 | } 61 | return nil 62 | } 63 | 64 | func GetAta(addr string, mint string) (solana.PublicKey, error) { 65 | pk, err := solana.PublicKeyFromBase58(addr) 66 | if err != nil { 67 | return solana.PublicKey{}, err 68 | } 69 | 70 | mintpk, err := solana.PublicKeyFromBase58(mint) 71 | if err != nil { 72 | return solana.PublicKey{}, err 73 | } 74 | 75 | ata, _, err := solana.FindAssociatedTokenAddress(pk, mintpk) 76 | if err != nil { 77 | return solana.PublicKey{}, err 78 | } 79 | 80 | return ata, nil 81 | 82 | } 83 | 84 | func GetAtaFromPk(pk solana.PublicKey, mintpk solana.PublicKey) (solana.PublicKey, error) { 85 | 86 | ata, _, err := solana.FindAssociatedTokenAddress(pk, mintpk) 87 | if err != nil { 88 | return solana.PublicKey{}, err 89 | } 90 | 91 | return ata, nil 92 | 93 | } 94 | 95 | func TransferBody(senderAddr string, receiverAddr string, amount *big.Int) ([]byte, error) { 96 | senderAddr = strings.TrimSpace(senderAddr) 97 | receiverAddr = strings.TrimSpace(receiverAddr) 98 | 99 | senderpk, err := solana.PublicKeyFromBase58(senderAddr) 100 | if err != nil { 101 | return nil, err 102 | } 103 | receiverpk, err := solana.PublicKeyFromBase58(receiverAddr) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | inst := system.NewTransferInstruction( 109 | amount.Uint64(), 110 | senderpk, 111 | receiverpk, 112 | ).Build() 113 | 114 | return ToBody([]solana.Instruction{inst}, nil) 115 | 116 | } 117 | 118 | func ToBody(insts []solana.Instruction, keypairs []SolanaKeypair) ([]byte, error) { 119 | body, err := ToSolanaBody(insts, keypairs, nil) 120 | if err != nil { 121 | return nil, err 122 | } 123 | return json.Marshal(body) 124 | } 125 | 126 | func ToSolanaBody(insts []solana.Instruction, keypairs []SolanaKeypair, lookupTables map[solana.PublicKey]solana.PublicKeySlice) (*SolanaBody, error) { 127 | body := SolanaBody{ 128 | Instructions: make([]SolanaInstruction, 0, len(insts)), 129 | } 130 | if keypairs != nil { 131 | body.Keypairs = keypairs 132 | } 133 | 134 | if lookupTables != nil { 135 | body.LookupTables = lookupTables 136 | } 137 | 138 | err := body.AddInstructions(insts) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | return &body, nil 144 | 145 | } 146 | -------------------------------------------------------------------------------- /txn/solana/spl.go: -------------------------------------------------------------------------------- 1 | package sol 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | 7 | "github.com/gagliardetto/solana-go" 8 | "github.com/gagliardetto/solana-go/programs/token" 9 | ) 10 | 11 | func SplApproveBody(senderAddr string, tokenAddr string, spenderAddr string, amount *big.Int, decimals int32) ([]byte, error) { 12 | senderAddr = strings.TrimSpace(senderAddr) 13 | tokenAddr = strings.TrimSpace(tokenAddr) 14 | spenderAddr = strings.TrimSpace(spenderAddr) 15 | 16 | senderpk, err := solana.PublicKeyFromBase58(senderAddr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | mintpk, err := solana.PublicKeyFromBase58(tokenAddr) 21 | if err != nil { 22 | return nil, err 23 | } 24 | spenderpk, err := solana.PublicKeyFromBase58(spenderAddr) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | senderAta, err := GetAtaFromPk(senderpk, mintpk) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | spenderAta, err := GetAtaFromPk(spenderpk, mintpk) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | inst := token.NewApproveCheckedInstruction( 40 | amount.Uint64(), 41 | uint8(decimals), 42 | senderAta, 43 | mintpk, 44 | spenderAta, 45 | senderpk, 46 | []solana.PublicKey{}, 47 | ).Build() 48 | 49 | return ToBody([]solana.Instruction{inst}, nil) 50 | 51 | } 52 | 53 | func SqlTransferBody(senderAddr string, tokenAddr string, receiverAddr string, amount *big.Int, decimals int32) ([]byte, error) { 54 | senderAddr = strings.TrimSpace(senderAddr) 55 | tokenAddr = strings.TrimSpace(tokenAddr) 56 | receiverAddr = strings.TrimSpace(receiverAddr) 57 | 58 | senderpk, err := solana.PublicKeyFromBase58(senderAddr) 59 | if err != nil { 60 | return nil, err 61 | } 62 | mintpk, err := solana.PublicKeyFromBase58(tokenAddr) 63 | if err != nil { 64 | return nil, err 65 | } 66 | receiverpk, err := solana.PublicKeyFromBase58(receiverAddr) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | senderAta, err := GetAtaFromPk(senderpk, mintpk) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | receiverAta, err := GetAtaFromPk(receiverpk, mintpk) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | inst := token.NewTransferCheckedInstruction( 82 | amount.Uint64(), 83 | uint8(decimals), 84 | senderAta, 85 | mintpk, 86 | receiverAta, 87 | senderpk, 88 | []solana.PublicKey{}, 89 | ).Build() 90 | 91 | return ToBody([]solana.Instruction{inst}, nil) 92 | 93 | } 94 | -------------------------------------------------------------------------------- /util/address.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/NethermindEth/starknet.go/curve" 10 | "github.com/ethereum/go-ethereum/common" 11 | ) 12 | 13 | func GetChecksumAddress(address string) (string, error) { 14 | address = strings.TrimSpace(address) 15 | if !strings.HasPrefix(address, "0x") && !strings.HasPrefix(address, "0X") { 16 | return address, nil 17 | } 18 | if len(address) == 42 { 19 | return GetChecksumAddress40(address) 20 | } else if len(address) == 66 { 21 | return GetChecksumAddress64(address) 22 | } else { 23 | return "", fmt.Errorf("unsupport address len: %s", address) 24 | } 25 | } 26 | 27 | func GetChecksumAddress40(address string) (string, error) { 28 | address = strings.TrimSpace(address) 29 | return common.HexToAddress(address).Hex(), nil 30 | } 31 | 32 | func GetChecksumAddress64(address string) (string, error) { 33 | address = strings.TrimSpace(address) 34 | address = strings.TrimLeft(strings.TrimPrefix(strings.ToLower(address), "0x"), "0") 35 | if len(address)%2 != 0 { 36 | address = "0" + address 37 | } 38 | if len(address) > 64 { 39 | return "", errors.New("address too long") 40 | } 41 | address64 := strings.Repeat("0", 64-len(address)) + address 42 | chars := strings.Split(address64, "") 43 | byteSlice, err := hex.DecodeString(address) 44 | if err != nil { 45 | return "", err 46 | } 47 | h, err := curve.Curve.StarknetKeccak(byteSlice) 48 | if err != nil { 49 | return "", err 50 | } 51 | hs := strings.TrimLeft(strings.TrimPrefix(h.String(), "0x"), "0") 52 | if len(hs) > 64 { 53 | return "", errors.New("hs too long") 54 | } 55 | hashed, err := hex.DecodeString(strings.Repeat("0", 64-len(hs)) + hs) 56 | if err != nil { 57 | return "", err 58 | } 59 | 60 | for i := 0; i < len(chars); i += 2 { 61 | if hashed[i>>1]>>4 >= 8 { 62 | chars[i] = strings.ToUpper(chars[i]) 63 | } 64 | if (hashed[i>>1] & 0x0f) >= 8 { 65 | chars[i+1] = strings.ToUpper(chars[i+1]) 66 | } 67 | } 68 | return "0x" + strings.Join(chars, ""), nil 69 | } 70 | -------------------------------------------------------------------------------- /util/common.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | func IsHexStringZero(hexString string) bool { 12 | // Remove "0x" prefix if it exists 13 | hexString = strings.TrimSpace(hexString) 14 | if len(hexString) >= 2 && (hexString[:2] == "0x" || hexString[:2] == "0X") { 15 | hexString = hexString[2:] 16 | } 17 | 18 | // Check if all characters in the hex string are '0' 19 | for _, ch := range hexString { 20 | if ch != '0' { 21 | return false 22 | } 23 | } 24 | return true 25 | } 26 | 27 | func GetJsonBigInt(itf interface{}) *big.Int { 28 | switch itf := itf.(type) { 29 | case float64: 30 | return big.NewInt(int64(itf)) 31 | case string: 32 | bi := new(big.Int) 33 | bi, success := bi.SetString(strings.TrimSpace(itf), 0) 34 | if success { 35 | return bi 36 | } else { 37 | return new(big.Int) 38 | } 39 | default: 40 | return new(big.Int) 41 | } 42 | } 43 | 44 | func FromUiString(amount string, decimals int32) (*big.Int, error) { 45 | // Convert the amount string to a big.Float 46 | amountFloat, _, err := new(big.Float).SetPrec(236).Parse(amount, 10) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // Scale the amount by 10^(decimals) 52 | scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) 53 | amountScaled := new(big.Float).SetPrec(236).Mul(amountFloat, new(big.Float).SetPrec(236).SetInt(scale)) 54 | 55 | // Convert the scaled amount to a big.Int 56 | amountBigInt := new(big.Int) 57 | amountScaled.Int(amountBigInt) 58 | 59 | return amountBigInt, nil 60 | } 61 | 62 | func FromUiFloat(amount float64, decimals int32) *big.Int { 63 | // Convert the amount string to a big.Float 64 | amountFloat := new(big.Float).SetPrec(236).SetFloat64(amount) 65 | 66 | // Scale the amount by 10^(decimals) 67 | scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) 68 | amountScaled := new(big.Float).SetPrec(236).Mul(amountFloat, new(big.Float).SetPrec(236).SetInt(scale)) 69 | 70 | // Convert the scaled amount to a big.Int 71 | amountBigInt := new(big.Int) 72 | amountScaled.Int(amountBigInt) 73 | 74 | return amountBigInt 75 | } 76 | 77 | func StringToUi(amountStr string, decimals int32) (*big.Float, error) { 78 | // Parse amount string to a big.Int 79 | amountBigInt, success := new(big.Int).SetString(amountStr, 10) 80 | if !success { 81 | return nil, fmt.Errorf("invalid amount string: %s", amountStr) 82 | } 83 | 84 | // Convert amount to a big.Float 85 | amountFloat := new(big.Float).SetPrec(236).SetInt(amountBigInt) 86 | 87 | // Scale down the amount by 10^(decimals) 88 | scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) 89 | divider := new(big.Float).SetPrec(236).SetInt(scale) 90 | amountFloat.Quo(amountFloat, divider) 91 | 92 | return amountFloat, nil 93 | } 94 | 95 | func BigIntToUi(amount *big.Int, decimals int32) *big.Float { 96 | // Convert amount to a big.Float 97 | amountFloat := new(big.Float).SetPrec(236).SetInt(amount) 98 | 99 | // Scale down the amount by 10^(decimals) 100 | scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil) 101 | divider := new(big.Float).SetPrec(236).SetInt(scale) 102 | amountFloat.Quo(amountFloat, divider) 103 | 104 | return amountFloat 105 | } 106 | 107 | func IsEvmAddress(address string, chainID int32) bool { 108 | return common.IsHexAddress(address) && chainID != 666666666 && chainID != 83797601 109 | } 110 | -------------------------------------------------------------------------------- /util/generate.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | var loc, _ = time.LoadLocation("Asia/Shanghai") 11 | 12 | func GenerateLogID() string { 13 | now := time.Now().In(loc) 14 | random := rand.New(rand.NewSource(now.UnixNano())) 15 | timestamp := now.Format("20060102150405") 16 | randomPart := fmt.Sprintf("%018d", random.Int63()) 17 | if len(randomPart) > 18 { 18 | randomPart = randomPart[:18] 19 | } 20 | return fmt.Sprintf("%s%s", timestamp, randomPart) 21 | } 22 | 23 | func GetLogId(ctx context.Context) string { 24 | logId := ctx.Value("logId") 25 | if LogId, ok := logId.(string); ok { 26 | return LogId 27 | } 28 | return "" 29 | } 30 | 31 | func WithLogIDCtx(ctx context.Context, logId string) context.Context { 32 | return context.WithValue(ctx, "logId", logId) 33 | } 34 | -------------------------------------------------------------------------------- /util/page.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func NormPage(page int) int { 4 | if page <= 0 { 5 | page = 1 6 | } 7 | return page 8 | } 9 | 10 | func NormPageSize(pageSize int) int { 11 | if pageSize <= 0 { 12 | pageSize = 10 13 | } 14 | if pageSize > 100 { 15 | pageSize = 100 16 | } 17 | return pageSize 18 | } 19 | --------------------------------------------------------------------------------