main.go 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. package main
  2. import (
  3. "context"
  4. "crypto/ecdsa"
  5. "errors"
  6. "fmt"
  7. "log"
  8. "math/big"
  9. "os"
  10. "strings"
  11. "time"
  12. "github.com/ethereum/go-ethereum/accounts/abi"
  13. "github.com/ethereum/go-ethereum/accounts/abi/bind"
  14. "github.com/ethereum/go-ethereum/common"
  15. "github.com/ethereum/go-ethereum/crypto"
  16. "github.com/ethereum/go-ethereum/ethclient"
  17. "github.com/joho/godotenv"
  18. "github.com/spf13/cobra"
  19. )
  20. var (
  21. rpcURL string
  22. coinAddr string
  23. tokenAddr string
  24. pkHex string
  25. )
  26. func loadEnv() {
  27. _ = godotenv.Load("../.env")
  28. _ = godotenv.Load(".env")
  29. if rpcURL == "" {
  30. rpcURL = os.Getenv("RPC_URL")
  31. if rpcURL == "" {
  32. rpcURL = os.Getenv("POLYGON_RPC_URL")
  33. if rpcURL == "" {
  34. rpcURL = os.Getenv("AMOY_RPC_URL")
  35. }
  36. }
  37. }
  38. if coinAddr == "" {
  39. coinAddr = os.Getenv("EASY_COIN_ADDR")
  40. }
  41. if tokenAddr == "" {
  42. tokenAddr = os.Getenv("EASY_TOKEN_ADDR")
  43. }
  44. if pkHex == "" {
  45. pkHex = os.Getenv("PRIVATE_KEY")
  46. if pkHex == "" {
  47. pkHex = os.Getenv("EASY_ADMIN_PRIVATE_KEY")
  48. if pkHex == "" {
  49. pkHex = os.Getenv("EASY_ADMIM_PRIVATE_KEY") // fallback to example typo
  50. }
  51. }
  52. }
  53. }
  54. func mustClient(ctx context.Context) (*ethclient.Client, *big.Int) {
  55. if rpcURL == "" {
  56. log.Fatal("RPC URL not set: use --rpc or set RPC_URL/POLYGON_RPC_URL/AMOY_RPC_URL in .env")
  57. }
  58. c, err := ethclient.DialContext(ctx, rpcURL)
  59. if err != nil {
  60. log.Fatalf("dial rpc: %v", err)
  61. }
  62. chainID, err := c.ChainID(ctx)
  63. if err != nil {
  64. log.Fatalf("get chain id: %v", err)
  65. }
  66. return c, chainID
  67. }
  68. func mustPrivKey() *ecdsa.PrivateKey {
  69. if pkHex == "" {
  70. log.Fatal("Private key not set: use --pk or set PRIVATE_KEY/EASY_ADMIN_PRIVATE_KEY in .env")
  71. }
  72. pk, err := crypto.HexToECDSA(strings.TrimPrefix(pkHex, "0x"))
  73. if err != nil {
  74. log.Fatalf("invalid private key: %v", err)
  75. }
  76. return pk
  77. }
  78. func fromHex32(s string) ([32]byte, error) {
  79. var out [32]byte
  80. b := common.FromHex(s)
  81. if len(b) != 32 {
  82. return out, fmt.Errorf("expected 32 bytes, got %d", len(b))
  83. }
  84. copy(out[:], b)
  85. return out, nil
  86. }
  87. func roleID(name string) [32]byte {
  88. upper := strings.ToUpper(name)
  89. if upper == "DEFAULT_ADMIN_ROLE" || upper == "DEFAULT_ADMIN" || upper == "ADMIN" {
  90. return [32]byte{}
  91. }
  92. // roles are keccak256("<ROLE>"), e.g. "PAUSER_ROLE"
  93. h := crypto.Keccak256([]byte(upper))
  94. var out [32]byte
  95. copy(out[:], h)
  96. return out
  97. }
  98. func mustAddress(hex string, envName string) common.Address {
  99. if hex == "" {
  100. log.Fatalf("missing address: set %s or pass a flag", envName)
  101. }
  102. if !common.IsHexAddress(hex) {
  103. log.Fatalf("invalid address for %s: %s", envName, hex)
  104. }
  105. return common.HexToAddress(hex)
  106. }
  107. func toWei(amount string) (*big.Int, error) {
  108. // parse base-unit integer (no decimals handling here)
  109. if strings.Contains(amount, ".") {
  110. return nil, errors.New("amount must be an integer in base units (no decimal point)")
  111. }
  112. z := new(big.Int)
  113. _, ok := z.SetString(amount, 10)
  114. if !ok {
  115. return nil, fmt.Errorf("invalid amount: %s", amount)
  116. }
  117. return z, nil
  118. }
  119. func boundContract(addr common.Address, abiJSON string, c *ethclient.Client) *bind.BoundContract {
  120. parsed, err := abi.JSON(strings.NewReader(abiJSON))
  121. if err != nil {
  122. log.Fatalf("parse abi: %v", err)
  123. }
  124. return bind.NewBoundContract(addr, parsed, c, c, c)
  125. }
  126. func txOpts(ctx context.Context, c *ethclient.Client, chainID *big.Int) *bind.TransactOpts {
  127. pk := mustPrivKey()
  128. auth, err := bind.NewKeyedTransactorWithChainID(pk, chainID)
  129. if err != nil {
  130. log.Fatalf("transactor: %v", err)
  131. }
  132. auth.Context = ctx
  133. // let node estimate gas; we can set a reasonable timeout
  134. return auth
  135. }
  136. func callOpts(ctx context.Context) *bind.CallOpts {
  137. return &bind.CallOpts{Context: ctx}
  138. }
  139. // ABI call helpers compatible with go-ethereum v1.14 BoundContract.Call
  140. func callOut(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) ([]interface{}, error) {
  141. var out []interface{}
  142. if err := con.Call(callOpts(ctx), &out, method, args...); err != nil {
  143. return nil, err
  144. }
  145. return out, nil
  146. }
  147. func callString(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) (string, error) {
  148. out, err := callOut(ctx, con, method, args...)
  149. if err != nil { return "", err }
  150. v, ok := out[0].(string)
  151. if !ok { return "", fmt.Errorf("unexpected %T", out[0]) }
  152. return v, nil
  153. }
  154. func callBool(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) (bool, error) {
  155. out, err := callOut(ctx, con, method, args...)
  156. if err != nil { return false, err }
  157. v, ok := out[0].(bool)
  158. if !ok { return false, fmt.Errorf("unexpected %T", out[0]) }
  159. return v, nil
  160. }
  161. func callUint8(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) (uint8, error) {
  162. out, err := callOut(ctx, con, method, args...)
  163. if err != nil { return 0, err }
  164. v, ok := out[0].(uint8)
  165. if !ok { return 0, fmt.Errorf("unexpected %T", out[0]) }
  166. return v, nil
  167. }
  168. func callBig(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) (*big.Int, error) {
  169. out, err := callOut(ctx, con, method, args...)
  170. if err != nil { return nil, err }
  171. v, ok := out[0].(*big.Int)
  172. if !ok { return nil, fmt.Errorf("unexpected %T", out[0]) }
  173. return v, nil
  174. }
  175. func callAddress(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) (common.Address, error) {
  176. out, err := callOut(ctx, con, method, args...)
  177. if err != nil { return common.Address{}, err }
  178. v, ok := out[0].(common.Address)
  179. if !ok { return common.Address{}, fmt.Errorf("unexpected %T", out[0]) }
  180. return v, nil
  181. }
  182. func callBytes32(ctx context.Context, con *bind.BoundContract, method string, args ...interface{}) ([32]byte, error) {
  183. out, err := callOut(ctx, con, method, args...)
  184. if err != nil { return [32]byte{}, err }
  185. v, ok := out[0].([32]byte)
  186. if !ok { return [32]byte{}, fmt.Errorf("unexpected %T", out[0]) }
  187. return v, nil
  188. }
  189. func cmdCoin() *cobra.Command {
  190. coinCmd := &cobra.Command{
  191. Use: "coin",
  192. Short: "Interact with EasyBRL (ERC20)",
  193. }
  194. // info
  195. coinCmd.AddCommand(&cobra.Command{
  196. Use: "info",
  197. Short: "Show ERC20 name, symbol, decimals, totalSupply, paused",
  198. Run: func(cmd *cobra.Command, args []string) {
  199. loadEnv()
  200. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  201. defer cancel()
  202. c, _ := mustClient(ctx)
  203. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  204. con := boundContract(addr, easyBRLStableABI, c)
  205. name, err := callString(ctx, con, "name"); if err != nil { log.Fatal(err) }
  206. symbol, err := callString(ctx, con, "symbol"); if err != nil { log.Fatal(err) }
  207. decimals, err := callUint8(ctx, con, "decimals"); if err != nil { log.Fatal(err) }
  208. total, err := callBig(ctx, con, "totalSupply"); if err != nil { log.Fatal(err) }
  209. paused, err := callBool(ctx, con, "paused"); if err != nil { log.Fatal(err) }
  210. fmt.Printf("name=%s symbol=%s decimals=%d totalSupply=%s paused=%v\n", name, symbol, decimals, total.String(), paused)
  211. },
  212. })
  213. // balance
  214. var balAddr string
  215. balCmd := &cobra.Command{
  216. Use: "balance",
  217. Short: "Get ERC20 balance of an address",
  218. Run: func(cmd *cobra.Command, args []string) {
  219. loadEnv(); if balAddr == "" { log.Fatal("--address required") }
  220. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  221. c, _ := mustClient(ctx)
  222. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  223. con := boundContract(addr, easyBRLStableABI, c)
  224. bal, err := callBig(ctx, con, "balanceOf", common.HexToAddress(balAddr)); if err != nil { log.Fatal(err) }
  225. fmt.Println(bal.String())
  226. },
  227. }
  228. balCmd.Flags().StringVar(&balAddr, "address", "", "Address to query")
  229. coinCmd.AddCommand(balCmd)
  230. // transfer
  231. var trTo, trAmt string
  232. trCmd := &cobra.Command{
  233. Use: "transfer",
  234. Short: "Transfer ERC20 tokens (base units)",
  235. Run: func(cmd *cobra.Command, args []string) {
  236. loadEnv(); if trTo == "" || trAmt == "" { log.Fatal("--to and --amount required") }
  237. amt, err := toWei(trAmt); if err != nil { log.Fatal(err) }
  238. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  239. c, chain := mustClient(ctx)
  240. auth := txOpts(ctx, c, chain)
  241. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  242. con := boundContract(addr, easyBRLStableABI, c)
  243. tx, err := con.Transact(auth, "transfer", common.HexToAddress(trTo), amt); if err != nil { log.Fatal(err) }
  244. fmt.Println(tx.Hash().Hex())
  245. },
  246. }
  247. trCmd.Flags().StringVar(&trTo, "to", "", "Recipient address")
  248. trCmd.Flags().StringVar(&trAmt, "amount", "", "Amount in base units")
  249. coinCmd.AddCommand(trCmd)
  250. // approve
  251. var apSp, apAmt string
  252. apCmd := &cobra.Command{
  253. Use: "approve",
  254. Short: "Approve spender (base units)",
  255. Run: func(cmd *cobra.Command, args []string) {
  256. loadEnv(); if apSp == "" || apAmt == "" { log.Fatal("--spender and --amount required") }
  257. amt, err := toWei(apAmt); if err != nil { log.Fatal(err) }
  258. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  259. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  260. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  261. con := boundContract(addr, easyBRLStableABI, c)
  262. tx, err := con.Transact(auth, "approve", common.HexToAddress(apSp), amt); if err != nil { log.Fatal(err) }
  263. fmt.Println(tx.Hash().Hex())
  264. },
  265. }
  266. apCmd.Flags().StringVar(&apSp, "spender", "", "Spender address")
  267. apCmd.Flags().StringVar(&apAmt, "amount", "", "Amount in base units")
  268. coinCmd.AddCommand(apCmd)
  269. // allowance
  270. var alOwn, alSp string
  271. alCmd := &cobra.Command{
  272. Use: "allowance",
  273. Short: "Check allowance(owner,spender)",
  274. Run: func(cmd *cobra.Command, args []string) {
  275. loadEnv(); if alOwn == "" || alSp == "" { log.Fatal("--owner and --spender required") }
  276. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  277. c, _ := mustClient(ctx)
  278. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  279. con := boundContract(addr, easyBRLStableABI, c)
  280. v, err := callBig(ctx, con, "allowance", common.HexToAddress(alOwn), common.HexToAddress(alSp)); if err != nil { log.Fatal(err) }
  281. fmt.Println(v.String())
  282. },
  283. }
  284. alCmd.Flags().StringVar(&alOwn, "owner", "", "Owner address")
  285. alCmd.Flags().StringVar(&alSp, "spender", "", "Spender address")
  286. coinCmd.AddCommand(alCmd)
  287. // transfer-from
  288. var tfFrom, tfTo, tfAmt string
  289. tfCmd := &cobra.Command{
  290. Use: "transfer-from",
  291. Short: "Transfer from (requires allowance)",
  292. Run: func(cmd *cobra.Command, args []string) {
  293. loadEnv(); if tfFrom == "" || tfTo == "" || tfAmt == "" { log.Fatal("--from --to --amount required") }
  294. amt, err := toWei(tfAmt); if err != nil { log.Fatal(err) }
  295. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  296. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  297. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  298. con := boundContract(addr, easyBRLStableABI, c)
  299. tx, err := con.Transact(auth, "transferFrom", common.HexToAddress(tfFrom), common.HexToAddress(tfTo), amt); if err != nil { log.Fatal(err) }
  300. fmt.Println(tx.Hash().Hex())
  301. },
  302. }
  303. tfCmd.Flags().StringVar(&tfFrom, "from", "", "From address")
  304. tfCmd.Flags().StringVar(&tfTo, "to", "", "To address")
  305. tfCmd.Flags().StringVar(&tfAmt, "amount", "", "Amount in base units")
  306. coinCmd.AddCommand(tfCmd)
  307. // paused
  308. coinCmd.AddCommand(&cobra.Command{Use: "paused", Short: "Is paused?", Run: func(cmd *cobra.Command, args []string) {
  309. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  310. c, _ := mustClient(ctx)
  311. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  312. con := boundContract(addr, easyBRLStableABI, c)
  313. p, err := callBool(ctx, con, "paused"); if err != nil { log.Fatal(err) }
  314. fmt.Println(p)
  315. }})
  316. // pause/unpause
  317. coinCmd.AddCommand(&cobra.Command{Use: "pause", Short: "Pause transfers", Run: func(cmd *cobra.Command, args []string) {
  318. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  319. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  320. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  321. con := boundContract(addr, easyBRLStableABI, c)
  322. tx, err := con.Transact(auth, "pause"); if err != nil { log.Fatal(err) }
  323. fmt.Println(tx.Hash().Hex())
  324. }})
  325. coinCmd.AddCommand(&cobra.Command{Use: "unpause", Short: "Unpause transfers", Run: func(cmd *cobra.Command, args []string) {
  326. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  327. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  328. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  329. con := boundContract(addr, easyBRLStableABI, c)
  330. tx, err := con.Transact(auth, "unpause"); if err != nil { log.Fatal(err) }
  331. fmt.Println(tx.Hash().Hex())
  332. }})
  333. // blacklist
  334. var blAcc string; var blStatus bool
  335. blCmd := &cobra.Command{Use: "set-blacklist", Short: "Set blacklist status", Run: func(cmd *cobra.Command, args []string) {
  336. loadEnv(); if blAcc=="" { log.Fatal("--account required") }
  337. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  338. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  339. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  340. con := boundContract(addr, easyBRLStableABI, c)
  341. tx, err := con.Transact(auth, "setBlacklist", common.HexToAddress(blAcc), blStatus); if err != nil { log.Fatal(err) }
  342. fmt.Println(tx.Hash().Hex())
  343. }}
  344. blCmd.Flags().StringVar(&blAcc, "account", "", "Account address")
  345. blCmd.Flags().BoolVar(&blStatus, "status", false, "Blacklist status")
  346. coinCmd.AddCommand(blCmd)
  347. var qblAcc string
  348. {
  349. cmd := &cobra.Command{Use: "is-blacklisted", Short: "Check blacklist", Run: func(cmd *cobra.Command, args []string) {
  350. loadEnv(); if qblAcc=="" { log.Fatal("--account required") }
  351. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  352. c, _ := mustClient(ctx)
  353. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  354. con := boundContract(addr, easyBRLStableABI, c)
  355. v, err := callBool(ctx, con, "isBlacklisted", common.HexToAddress(qblAcc)); if err != nil { log.Fatal(err) }
  356. fmt.Println(v)
  357. }}
  358. cmd.Flags().StringVar(&qblAcc, "account", "", "Account address")
  359. coinCmd.AddCommand(cmd)
  360. }
  361. // mint
  362. var miTo, miAmt string
  363. miCmd := &cobra.Command{Use: "mint", Short: "Mint tokens (admin only)", Run: func(cmd *cobra.Command, args []string) {
  364. loadEnv(); if miTo==""||miAmt=="" { log.Fatal("--to --amount required") }
  365. amt, err := toWei(miAmt); if err!=nil { log.Fatal(err) }
  366. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  367. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  368. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  369. con := boundContract(addr, easyBRLStableABI, c)
  370. tx, err := con.Transact(auth, "mint", common.HexToAddress(miTo), amt); if err != nil { log.Fatal(err) }
  371. fmt.Println(tx.Hash().Hex())
  372. }}
  373. miCmd.Flags().StringVar(&miTo, "to", "", "Recipient address")
  374. miCmd.Flags().StringVar(&miAmt, "amount", "", "Amount in base units")
  375. coinCmd.AddCommand(miCmd)
  376. // burn & burn-from
  377. var buAmt string
  378. {
  379. cmd := &cobra.Command{Use: "burn", Short: "Burn own balance (admin only)", Run: func(cmd *cobra.Command, args []string) {
  380. loadEnv(); if buAmt=="" { log.Fatal("--amount required") }
  381. amt, err := toWei(buAmt); if err!=nil { log.Fatal(err) }
  382. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  383. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  384. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  385. con := boundContract(addr, easyBRLStableABI, c)
  386. tx, err := con.Transact(auth, "burn", amt); if err != nil { log.Fatal(err) }
  387. fmt.Println(tx.Hash().Hex())
  388. }}
  389. cmd.Flags().StringVar(&buAmt, "amount", "", "Amount in base units")
  390. coinCmd.AddCommand(cmd)
  391. }
  392. var bfAcc, bfAmt string
  393. bfCmd := &cobra.Command{Use: "burn-from", Short: "Burn from account (admin only)", Run: func(cmd *cobra.Command, args []string) {
  394. loadEnv(); if bfAcc==""||bfAmt=="" { log.Fatal("--account --amount required") }
  395. amt, err := toWei(bfAmt); if err!=nil { log.Fatal(err) }
  396. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  397. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  398. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  399. con := boundContract(addr, easyBRLStableABI, c)
  400. tx, err := con.Transact(auth, "burnFrom", common.HexToAddress(bfAcc), amt); if err != nil { log.Fatal(err) }
  401. fmt.Println(tx.Hash().Hex())
  402. }}
  403. bfCmd.Flags().StringVar(&bfAcc, "account", "", "Account")
  404. bfCmd.Flags().StringVar(&bfAmt, "amount", "", "Amount in base units")
  405. coinCmd.AddCommand(bfCmd)
  406. // roles
  407. var rrRole, rrAcc string
  408. grCmd := &cobra.Command{Use: "grant-role", Short: "Grant role to account", Run: func(cmd *cobra.Command, args []string) {
  409. loadEnv(); if rrRole==""||rrAcc=="" { log.Fatal("--role --account required") }
  410. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  411. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  412. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  413. con := boundContract(addr, easyBRLStableABI, c)
  414. role := roleID(rrRole)
  415. tx, err := con.Transact(auth, "grantRole", role, common.HexToAddress(rrAcc)); if err != nil { log.Fatal(err) }
  416. fmt.Println(tx.Hash().Hex())
  417. }}
  418. grCmd.Flags().StringVar(&rrRole, "role", "", "Role name (DEFAULT_ADMIN_ROLE, PAUSER_ROLE, MINTER_ROLE, COMPLIANCE_ROLE)")
  419. grCmd.Flags().StringVar(&rrAcc, "account", "", "Account address")
  420. coinCmd.AddCommand(grCmd)
  421. rrRole, rrAcc = "", ""
  422. rvCmd := &cobra.Command{Use: "revoke-role", Short: "Revoke role from account", Run: func(cmd *cobra.Command, args []string) {
  423. loadEnv(); if rrRole==""||rrAcc=="" { log.Fatal("--role --account required") }
  424. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  425. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  426. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  427. con := boundContract(addr, easyBRLStableABI, c)
  428. role := roleID(rrRole)
  429. tx, err := con.Transact(auth, "revokeRole", role, common.HexToAddress(rrAcc)); if err != nil { log.Fatal(err) }
  430. fmt.Println(tx.Hash().Hex())
  431. }}
  432. rvCmd.Flags().StringVar(&rrRole, "role", "", "Role name")
  433. rvCmd.Flags().StringVar(&rrAcc, "account", "", "Account address")
  434. coinCmd.AddCommand(rvCmd)
  435. rrRole, rrAcc = "", ""
  436. hasCmd := &cobra.Command{Use: "has-role", Short: "Check role for account", Run: func(cmd *cobra.Command, args []string) {
  437. loadEnv(); if rrRole==""||rrAcc=="" { log.Fatal("--role --account required") }
  438. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  439. c, _ := mustClient(ctx)
  440. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  441. con := boundContract(addr, easyBRLStableABI, c)
  442. role := roleID(rrRole)
  443. v, err := callBool(ctx, con, "hasRole", role, common.HexToAddress(rrAcc)); if err != nil { log.Fatal(err) }
  444. fmt.Println(v)
  445. }}
  446. hasCmd.Flags().StringVar(&rrRole, "role", "", "Role name")
  447. hasCmd.Flags().StringVar(&rrAcc, "account", "", "Account address")
  448. coinCmd.AddCommand(hasCmd)
  449. var gcAcc string
  450. {
  451. cmd := &cobra.Command{Use: "grant-compliance-role", Short: "Grant COMPLIANCE_ROLE via helper", Run: func(cmd *cobra.Command, args []string) {
  452. loadEnv(); if gcAcc=="" { log.Fatal("--account required") }
  453. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  454. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  455. addr := mustAddress(coinAddr, "EASY_COIN_ADDR")
  456. con := boundContract(addr, easyBRLStableABI, c)
  457. tx, err := con.Transact(auth, "grantComplianceRole", common.HexToAddress(gcAcc)); if err != nil { log.Fatal(err) }
  458. fmt.Println(tx.Hash().Hex())
  459. }}
  460. cmd.Flags().StringVar(&gcAcc, "account", "", "Account address")
  461. coinCmd.AddCommand(cmd)
  462. }
  463. return coinCmd
  464. }
  465. func cmdToken() *cobra.Command {
  466. tokCmd := &cobra.Command{Use: "token", Short: "Interact with EasyToken (ERC721)"}
  467. // info
  468. tokCmd.AddCommand(&cobra.Command{Use: "info", Short: "Show ERC721 name, symbol, paused", Run: func(cmd *cobra.Command, args []string) {
  469. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  470. c, _ := mustClient(ctx)
  471. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  472. con := boundContract(addr, easyTokenDocumentABI, c)
  473. name, err := callString(ctx, con, "name"); if err != nil { log.Fatal(err) }
  474. symbol, err := callString(ctx, con, "symbol"); if err != nil { log.Fatal(err) }
  475. paused, err := callBool(ctx, con, "paused"); if err != nil { log.Fatal(err) }
  476. fmt.Printf("name=%s symbol=%s paused=%v\n", name, symbol, paused)
  477. }})
  478. // owner-of
  479. var ooID string
  480. {
  481. cmd := &cobra.Command{Use: "owner-of", Short: "Owner of tokenId", Run: func(cmd *cobra.Command, args []string) {
  482. loadEnv(); if ooID=="" { log.Fatal("--token-id required") }
  483. id, ok := new(big.Int).SetString(ooID, 10); if !ok { log.Fatal("invalid token-id") }
  484. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  485. c, _ := mustClient(ctx)
  486. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  487. con := boundContract(addr, easyTokenDocumentABI, c)
  488. owner, err := callAddress(ctx, con, "ownerOf", id); if err != nil { log.Fatal(err) }
  489. fmt.Println(owner.Hex())
  490. }}
  491. cmd.Flags().StringVar(&ooID, "token-id", "", "Token ID")
  492. tokCmd.AddCommand(cmd)
  493. }
  494. {
  495. cmd := &cobra.Command{Use: "get-info [token-id]", Short: "Show owner and content", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) {
  496. loadEnv()
  497. id, ok := new(big.Int).SetString(args[0], 10); if !ok { log.Fatal("invalid token-id") }
  498. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  499. c, _ := mustClient(ctx)
  500. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  501. con := boundContract(addr, easyTokenDocumentABI, c)
  502. owner, err := callAddress(ctx, con, "ownerOf", id); if err != nil { log.Fatal(err) }
  503. content, err := callString(ctx, con, "contentOf", id); if err != nil { log.Fatal(err) }
  504. fmt.Printf("owner=%s\n", owner.Hex())
  505. fmt.Printf("content=%s\n", content)
  506. }}
  507. tokCmd.AddCommand(cmd)
  508. }
  509. // balance
  510. var tbAddr string
  511. {
  512. cmd := &cobra.Command{Use: "balance", Short: "Number of NFTs for address", Run: func(cmd *cobra.Command, args []string) {
  513. loadEnv(); if tbAddr=="" { log.Fatal("--address required") }
  514. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  515. c, _ := mustClient(ctx)
  516. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  517. con := boundContract(addr, easyTokenDocumentABI, c)
  518. bal, err := callBig(ctx, con, "balanceOf", common.HexToAddress(tbAddr)); if err != nil { log.Fatal(err) }
  519. fmt.Println(bal.String())
  520. }}
  521. cmd.Flags().StringVar(&tbAddr, "address", "", "Address")
  522. tokCmd.AddCommand(cmd)
  523. }
  524. // token-uri
  525. var tuID string
  526. {
  527. cmd := &cobra.Command{Use: "token-uri", Short: "Get tokenURI", Run: func(cmd *cobra.Command, args []string) {
  528. loadEnv(); if tuID=="" { log.Fatal("--token-id required") }
  529. id, ok := new(big.Int).SetString(tuID, 10); if !ok { log.Fatal("invalid token-id") }
  530. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  531. c, _ := mustClient(ctx)
  532. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  533. con := boundContract(addr, easyTokenDocumentABI, c)
  534. uri, err := callString(ctx, con, "tokenURI", id); if err != nil { log.Fatal(err) }
  535. fmt.Println(uri)
  536. }}
  537. cmd.Flags().StringVar(&tuID, "token-id", "", "Token ID")
  538. tokCmd.AddCommand(cmd)
  539. }
  540. // doc-hash
  541. var dhID string
  542. {
  543. cmd := &cobra.Command{Use: "doc-hash", Short: "Get document hash", Run: func(cmd *cobra.Command, args []string) {
  544. loadEnv(); if dhID=="" { log.Fatal("--token-id required") }
  545. id, ok := new(big.Int).SetString(dhID, 10); if !ok { log.Fatal("invalid token-id") }
  546. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  547. c, _ := mustClient(ctx)
  548. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  549. con := boundContract(addr, easyTokenDocumentABI, c)
  550. h, err := callBytes32(ctx, con, "documentHashOf", id); if err != nil { log.Fatal(err) }
  551. fmt.Println("0x" + common.Bytes2Hex(h[:]))
  552. }}
  553. cmd.Flags().StringVar(&dhID, "token-id", "", "Token ID")
  554. tokCmd.AddCommand(cmd)
  555. }
  556. // appraisal
  557. var apID string
  558. {
  559. cmd := &cobra.Command{Use: "appraisal", Short: "Get appraisal value (base units)", Run: func(cmd *cobra.Command, args []string) {
  560. loadEnv(); if apID=="" { log.Fatal("--token-id required") }
  561. id, ok := new(big.Int).SetString(apID, 10); if !ok { log.Fatal("invalid token-id") }
  562. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  563. c, _ := mustClient(ctx)
  564. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  565. con := boundContract(addr, easyTokenDocumentABI, c)
  566. v, err := callBig(ctx, con, "appraisalOf", id); if err != nil { log.Fatal(err) }
  567. fmt.Println(v.String())
  568. }}
  569. cmd.Flags().StringVar(&apID, "token-id", "", "Token ID")
  570. tokCmd.AddCommand(cmd)
  571. }
  572. // set-token-uri
  573. var stuID, stuURI string
  574. {
  575. cmd := &cobra.Command{Use: "set-token-uri", Short: "Set token URI (METADATA_ROLE)", Run: func(cmd *cobra.Command, args []string) {
  576. loadEnv(); if stuID==""||stuURI=="" { log.Fatal("--token-id --uri required") }
  577. id, ok := new(big.Int).SetString(stuID, 10); if !ok { log.Fatal("invalid token-id") }
  578. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  579. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  580. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  581. con := boundContract(addr, easyTokenDocumentABI, c)
  582. tx, err := con.Transact(auth, "setTokenURI", id, stuURI); if err != nil { log.Fatal(err) }
  583. fmt.Println(tx.Hash().Hex())
  584. }}
  585. cmd.Flags().StringVar(&stuID, "token-id", "", "Token ID")
  586. cmd.Flags().StringVar(&stuURI, "uri", "", "New URI")
  587. tokCmd.AddCommand(cmd)
  588. }
  589. // set-appraisal
  590. var sapID, sapVal string
  591. {
  592. cmd := &cobra.Command{Use: "set-appraisal", Short: "Set appraisal value (base units)", Run: func(cmd *cobra.Command, args []string) {
  593. loadEnv(); if sapID==""||sapVal=="" { log.Fatal("--token-id --value required") }
  594. id, ok := new(big.Int).SetString(sapID, 10); if !ok { log.Fatal("invalid token-id") }
  595. val, err := toWei(sapVal); if err != nil { log.Fatal(err) }
  596. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  597. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  598. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  599. con := boundContract(addr, easyTokenDocumentABI, c)
  600. tx, err := con.Transact(auth, "setAppraisal", id, val); if err != nil { log.Fatal(err) }
  601. fmt.Println(tx.Hash().Hex())
  602. }}
  603. cmd.Flags().StringVar(&sapID, "token-id", "", "Token ID")
  604. cmd.Flags().StringVar(&sapVal, "value", "", "Appraisal in base units")
  605. tokCmd.AddCommand(cmd)
  606. }
  607. // mint content
  608. {
  609. cmd := &cobra.Command{Use: "mint [content]", Short: "Mint new token content (<=20 chars)", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) {
  610. content := args[0]
  611. if content == "" { log.Fatal("content required") }
  612. if len([]byte(content)) > 20 { log.Fatal("content must be at most 20 bytes") }
  613. loadEnv()
  614. ctx, cancel := context.WithTimeout(context.Background(), 240*time.Second); defer cancel()
  615. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  616. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  617. con := boundContract(addr, easyTokenDocumentABI, c)
  618. sender := crypto.PubkeyToAddress(mustPrivKey().PublicKey)
  619. tx, err := con.Transact(auth, "mintContent", sender, content); if err != nil { log.Fatal(err) }
  620. receipt, err := bind.WaitMined(ctx, c, tx); if err != nil { log.Fatal(err) }
  621. mintSig := crypto.Keccak256Hash([]byte("ContentMinted(uint256,address,string)"))
  622. tokenID := new(big.Int)
  623. for _, lg := range receipt.Logs {
  624. if lg.Address == addr && len(lg.Topics) >= 3 && lg.Topics[0] == mintSig {
  625. tokenID.SetBytes(lg.Topics[1].Bytes())
  626. break
  627. }
  628. }
  629. fmt.Printf("tx=%s\n", tx.Hash().Hex())
  630. if tokenID.Sign() > 0 {
  631. fmt.Printf("tokenId=%s\n", tokenID.String())
  632. }
  633. }}
  634. tokCmd.AddCommand(cmd)
  635. }
  636. // transfer & safe-transfer
  637. var ttTo, ttID string
  638. {
  639. cmd := &cobra.Command{Use: "transfer", Short: "transferFrom(sender,to,tokenId)", Run: func(cmd *cobra.Command, args []string) {
  640. loadEnv(); if ttTo==""||ttID=="" { log.Fatal("--to --token-id required") }
  641. id, ok := new(big.Int).SetString(ttID, 10); if !ok { log.Fatal("invalid token-id") }
  642. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  643. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  644. sender := crypto.PubkeyToAddress(mustPrivKey().PublicKey)
  645. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  646. con := boundContract(addr, easyTokenDocumentABI, c)
  647. tx, err := con.Transact(auth, "safeTransferFrom", sender, common.HexToAddress(ttTo), id); if err != nil { log.Fatal(err) }
  648. fmt.Println(tx.Hash().Hex())
  649. }}
  650. cmd.Flags().StringVar(&ttTo, "to", "", "Recipient address")
  651. cmd.Flags().StringVar(&ttID, "token-id", "", "Token ID")
  652. tokCmd.AddCommand(cmd)
  653. }
  654. var stTo, stID string
  655. {
  656. cmd := &cobra.Command{Use: "safe-transfer", Short: "safeTransferFrom(sender,to,tokenId)", Run: func(cmd *cobra.Command, args []string) {
  657. loadEnv(); if stTo==""||stID=="" { log.Fatal("--to --token-id required") }
  658. id, ok := new(big.Int).SetString(stID, 10); if !ok { log.Fatal("invalid token-id") }
  659. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  660. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  661. sender := crypto.PubkeyToAddress(mustPrivKey().PublicKey)
  662. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  663. con := boundContract(addr, easyTokenDocumentABI, c)
  664. tx, err := con.Transact(auth, "safeTransferFrom", sender, common.HexToAddress(stTo), id); if err != nil { log.Fatal(err) }
  665. fmt.Println(tx.Hash().Hex())
  666. }}
  667. cmd.Flags().StringVar(&stTo, "to", "", "Recipient address")
  668. cmd.Flags().StringVar(&stID, "token-id", "", "Token ID")
  669. tokCmd.AddCommand(cmd)
  670. }
  671. // approvals
  672. var apTo string; var apID2 string
  673. {
  674. cmd := &cobra.Command{Use: "approve", Short: "Approve address for tokenId", Run: func(cmd *cobra.Command, args []string) {
  675. loadEnv(); if apTo==""||apID2=="" { log.Fatal("--to --token-id required") }
  676. id, ok := new(big.Int).SetString(apID2, 10); if !ok { log.Fatal("invalid token-id") }
  677. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  678. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  679. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  680. con := boundContract(addr, easyTokenDocumentABI, c)
  681. tx, err := con.Transact(auth, "approve", common.HexToAddress(apTo), id); if err != nil { log.Fatal(err) }
  682. fmt.Println(tx.Hash().Hex())
  683. }}
  684. cmd.Flags().StringVar(&apTo, "to", "", "Approved address")
  685. cmd.Flags().StringVar(&apID2, "token-id", "", "Token ID")
  686. tokCmd.AddCommand(cmd)
  687. }
  688. var saoOp string; var saoApproved bool
  689. {
  690. cmd := &cobra.Command{Use: "set-approval-for-all", Short: "Set operator approval", Run: func(cmd *cobra.Command, args []string) {
  691. loadEnv(); if saoOp=="" { log.Fatal("--operator required") }
  692. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  693. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  694. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  695. con := boundContract(addr, easyTokenDocumentABI, c)
  696. tx, err := con.Transact(auth, "setApprovalForAll", common.HexToAddress(saoOp), saoApproved); if err != nil { log.Fatal(err) }
  697. fmt.Println(tx.Hash().Hex())
  698. }}
  699. cmd.Flags().StringVar(&saoOp, "operator", "", "Operator address")
  700. cmd.Flags().BoolVar(&saoApproved, "approved", false, "Approved status")
  701. tokCmd.AddCommand(cmd)
  702. }
  703. // getters for approvals
  704. var gaID string
  705. {
  706. cmd := &cobra.Command{Use: "get-approved", Short: "Get approved for tokenId", Run: func(cmd *cobra.Command, args []string) {
  707. loadEnv(); if gaID=="" { log.Fatal("--token-id required") }
  708. id, ok := new(big.Int).SetString(gaID, 10); if !ok { log.Fatal("invalid token-id") }
  709. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  710. c, _ := mustClient(ctx)
  711. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  712. con := boundContract(addr, easyTokenDocumentABI, c)
  713. a, err := callAddress(ctx, con, "getApproved", id); if err != nil { log.Fatal(err) }
  714. fmt.Println(a.Hex())
  715. }}
  716. cmd.Flags().StringVar(&gaID, "token-id", "", "Token ID")
  717. tokCmd.AddCommand(cmd)
  718. }
  719. var iaOwner, iaOp string
  720. {
  721. cmd := &cobra.Command{Use: "is-approved-for-all", Short: "Is operator approved for owner", Run: func(cmd *cobra.Command, args []string) {
  722. loadEnv(); if iaOwner==""||iaOp=="" { log.Fatal("--owner --operator required") }
  723. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  724. c, _ := mustClient(ctx)
  725. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  726. con := boundContract(addr, easyTokenDocumentABI, c)
  727. v, err := callBool(ctx, con, "isApprovedForAll", common.HexToAddress(iaOwner), common.HexToAddress(iaOp)); if err != nil { log.Fatal(err) }
  728. fmt.Println(v)
  729. }}
  730. cmd.Flags().StringVar(&iaOwner, "owner", "", "Owner address")
  731. cmd.Flags().StringVar(&iaOp, "operator", "", "Operator address")
  732. tokCmd.AddCommand(cmd)
  733. }
  734. // pause/unpause/paused
  735. tokCmd.AddCommand(&cobra.Command{Use: "paused", Short: "Is paused?", Run: func(cmd *cobra.Command, args []string) {
  736. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  737. c, _ := mustClient(ctx)
  738. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  739. con := boundContract(addr, easyTokenDocumentABI, c)
  740. p, err := callBool(ctx, con, "paused"); if err != nil { log.Fatal(err) }
  741. fmt.Println(p)
  742. }})
  743. tokCmd.AddCommand(&cobra.Command{Use: "pause", Short: "Pause transfers", Run: func(cmd *cobra.Command, args []string) {
  744. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  745. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  746. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  747. con := boundContract(addr, easyTokenDocumentABI, c)
  748. tx, err := con.Transact(auth, "pause"); if err != nil { log.Fatal(err) }
  749. fmt.Println(tx.Hash().Hex())
  750. }})
  751. tokCmd.AddCommand(&cobra.Command{Use: "unpause", Short: "Unpause transfers", Run: func(cmd *cobra.Command, args []string) {
  752. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  753. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  754. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  755. con := boundContract(addr, easyTokenDocumentABI, c)
  756. tx, err := con.Transact(auth, "unpause"); if err != nil { log.Fatal(err) }
  757. fmt.Println(tx.Hash().Hex())
  758. }})
  759. // blacklist
  760. var tblAcc string; var tblStatus bool
  761. {
  762. cmd := &cobra.Command{Use: "set-blacklist", Short: "Set blacklist status", Run: func(cmd *cobra.Command, args []string) {
  763. loadEnv(); if tblAcc=="" { log.Fatal("--account required") }
  764. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  765. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  766. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  767. con := boundContract(addr, easyTokenDocumentABI, c)
  768. tx, err := con.Transact(auth, "setBlacklist", common.HexToAddress(tblAcc), tblStatus); if err != nil { log.Fatal(err) }
  769. fmt.Println(tx.Hash().Hex())
  770. }}
  771. cmd.Flags().StringVar(&tblAcc, "account", "", "Account")
  772. cmd.Flags().BoolVar(&tblStatus, "status", false, "Blacklist status")
  773. tokCmd.AddCommand(cmd)
  774. }
  775. var tqblAcc string
  776. {
  777. cmd := &cobra.Command{Use: "is-blacklisted", Short: "Check blacklist", Run: func(cmd *cobra.Command, args []string) {
  778. loadEnv(); if tqblAcc=="" { log.Fatal("--account required") }
  779. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  780. c, _ := mustClient(ctx)
  781. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  782. con := boundContract(addr, easyTokenDocumentABI, c)
  783. v, err := callBool(ctx, con, "isBlacklisted", common.HexToAddress(tqblAcc)); if err != nil { log.Fatal(err) }
  784. fmt.Println(v)
  785. }}
  786. cmd.Flags().StringVar(&tqblAcc, "account", "", "Account")
  787. tokCmd.AddCommand(cmd)
  788. }
  789. // roles
  790. var trRole, trAcc string
  791. {
  792. cmd := &cobra.Command{Use: "grant-role", Short: "Grant role", Run: func(cmd *cobra.Command, args []string) {
  793. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  794. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  795. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  796. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  797. con := boundContract(addr, easyTokenDocumentABI, c)
  798. role := roleID(trRole)
  799. tx, err := con.Transact(auth, "grantRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  800. fmt.Println(tx.Hash().Hex())
  801. }}
  802. cmd.Flags().StringVar(&trRole, "role", "", "Role name (DEFAULT_ADMIN_ROLE, PAUSER_ROLE, MINTER_ROLE, METADATA_ROLE, APPRAISER_ROLE, COMPLIANCE_ROLE)")
  803. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  804. tokCmd.AddCommand(cmd)
  805. }
  806. trRole, trAcc = "", ""
  807. {
  808. cmd := &cobra.Command{Use: "revoke-role", Short: "Revoke role", Run: func(cmd *cobra.Command, args []string) {
  809. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  810. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  811. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  812. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  813. con := boundContract(addr, easyTokenDocumentABI, c)
  814. role := roleID(trRole)
  815. tx, err := con.Transact(auth, "revokeRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  816. fmt.Println(tx.Hash().Hex())
  817. }}
  818. cmd.Flags().StringVar(&trRole, "role", "", "Role name")
  819. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  820. tokCmd.AddCommand(cmd)
  821. }
  822. trRole, trAcc = "", ""
  823. {
  824. cmd := &cobra.Command{Use: "has-role", Short: "Has role?", Run: func(cmd *cobra.Command, args []string) {
  825. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  826. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  827. c, _ := mustClient(ctx)
  828. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  829. con := boundContract(addr, easyTokenDocumentABI, c)
  830. role := roleID(trRole)
  831. v, err := callBool(ctx, con, "hasRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  832. fmt.Println(v)
  833. }}
  834. cmd.Flags().StringVar(&trRole, "role", "", "Role name")
  835. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  836. tokCmd.AddCommand(cmd)
  837. }
  838. var tgcAcc string
  839. {
  840. cmd := &cobra.Command{Use: "grant-compliance-role", Short: "Grant COMPLIANCE_ROLE via helper", Run: func(cmd *cobra.Command, args []string) {
  841. loadEnv(); if tgcAcc=="" { log.Fatal("--account required") }
  842. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  843. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  844. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  845. con := boundContract(addr, easyTokenDocumentABI, c)
  846. tx, err := con.Transact(auth, "grantComplianceRole", common.HexToAddress(tgcAcc)); if err != nil { log.Fatal(err) }
  847. fmt.Println(tx.Hash().Hex())
  848. }}
  849. cmd.Flags().StringVar(&tgcAcc, "account", "", "Account address")
  850. tokCmd.AddCommand(cmd)
  851. }
  852. return tokCmd
  853. }
  854. func cmdPolygon() *cobra.Command {
  855. pg := &cobra.Command{Use: "polygon", Short: "Polygon utilities"}
  856. pg.AddCommand(&cobra.Command{Use: "create-new-address", Short: "Generate a new wallet", Run: func(cmd *cobra.Command, args []string) {
  857. pk, err := crypto.GenerateKey(); if err != nil { log.Fatal(err) }
  858. pkHex := "0x" + common.Bytes2Hex(crypto.FromECDSA(pk))
  859. pubHex := "0x" + common.Bytes2Hex(crypto.FromECDSAPub(&pk.PublicKey))
  860. addr := crypto.PubkeyToAddress(pk.PublicKey).Hex()
  861. fmt.Printf("privateKey=%s\npublicKey=%s\naddress=%s\n", pkHex, pubHex, addr)
  862. }})
  863. return pg
  864. }
  865. func main() {
  866. root := &cobra.Command{Use: "sdk", Short: "CLI for EasyBRL (coin) and EasyToken (token)"}
  867. root.PersistentFlags().StringVar(&rpcURL, "rpc", "", "RPC URL (overrides .env)")
  868. root.PersistentFlags().StringVar(&pkHex, "pk", "", "Private key hex (overrides .env)")
  869. root.PersistentFlags().StringVar(&coinAddr, "coin-addr", coinAddr, "ERC20 contract address (overrides .env)")
  870. root.PersistentFlags().StringVar(&tokenAddr, "token-addr", tokenAddr, "ERC721 contract address (overrides .env)")
  871. root.AddCommand(cmdCoin())
  872. root.AddCommand(cmdToken())
  873. root.AddCommand(cmdPolygon())
  874. if err := root.Execute(); err != nil {
  875. os.Exit(1)
  876. }
  877. }