main.go 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  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. // balance
  495. var tbAddr string
  496. {
  497. cmd := &cobra.Command{Use: "balance", Short: "Number of NFTs for address", Run: func(cmd *cobra.Command, args []string) {
  498. loadEnv(); if tbAddr=="" { log.Fatal("--address required") }
  499. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  500. c, _ := mustClient(ctx)
  501. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  502. con := boundContract(addr, easyTokenDocumentABI, c)
  503. bal, err := callBig(ctx, con, "balanceOf", common.HexToAddress(tbAddr)); if err != nil { log.Fatal(err) }
  504. fmt.Println(bal.String())
  505. }}
  506. cmd.Flags().StringVar(&tbAddr, "address", "", "Address")
  507. tokCmd.AddCommand(cmd)
  508. }
  509. // token-uri
  510. var tuID string
  511. {
  512. cmd := &cobra.Command{Use: "token-uri", Short: "Get tokenURI", Run: func(cmd *cobra.Command, args []string) {
  513. loadEnv(); if tuID=="" { log.Fatal("--token-id required") }
  514. id, ok := new(big.Int).SetString(tuID, 10); if !ok { log.Fatal("invalid token-id") }
  515. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  516. c, _ := mustClient(ctx)
  517. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  518. con := boundContract(addr, easyTokenDocumentABI, c)
  519. uri, err := callString(ctx, con, "tokenURI", id); if err != nil { log.Fatal(err) }
  520. fmt.Println(uri)
  521. }}
  522. cmd.Flags().StringVar(&tuID, "token-id", "", "Token ID")
  523. tokCmd.AddCommand(cmd)
  524. }
  525. // doc-hash
  526. var dhID string
  527. {
  528. cmd := &cobra.Command{Use: "doc-hash", Short: "Get document hash", Run: func(cmd *cobra.Command, args []string) {
  529. loadEnv(); if dhID=="" { log.Fatal("--token-id required") }
  530. id, ok := new(big.Int).SetString(dhID, 10); if !ok { log.Fatal("invalid token-id") }
  531. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  532. c, _ := mustClient(ctx)
  533. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  534. con := boundContract(addr, easyTokenDocumentABI, c)
  535. h, err := callBytes32(ctx, con, "documentHashOf", id); if err != nil { log.Fatal(err) }
  536. fmt.Println("0x" + common.Bytes2Hex(h[:]))
  537. }}
  538. cmd.Flags().StringVar(&dhID, "token-id", "", "Token ID")
  539. tokCmd.AddCommand(cmd)
  540. }
  541. // appraisal
  542. var apID string
  543. {
  544. cmd := &cobra.Command{Use: "appraisal", Short: "Get appraisal value (base units)", Run: func(cmd *cobra.Command, args []string) {
  545. loadEnv(); if apID=="" { log.Fatal("--token-id required") }
  546. id, ok := new(big.Int).SetString(apID, 10); if !ok { log.Fatal("invalid token-id") }
  547. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  548. c, _ := mustClient(ctx)
  549. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  550. con := boundContract(addr, easyTokenDocumentABI, c)
  551. v, err := callBig(ctx, con, "appraisalOf", id); if err != nil { log.Fatal(err) }
  552. fmt.Println(v.String())
  553. }}
  554. cmd.Flags().StringVar(&apID, "token-id", "", "Token ID")
  555. tokCmd.AddCommand(cmd)
  556. }
  557. // set-token-uri
  558. var stuID, stuURI string
  559. {
  560. cmd := &cobra.Command{Use: "set-token-uri", Short: "Set token URI (METADATA_ROLE)", Run: func(cmd *cobra.Command, args []string) {
  561. loadEnv(); if stuID==""||stuURI=="" { log.Fatal("--token-id --uri required") }
  562. id, ok := new(big.Int).SetString(stuID, 10); if !ok { log.Fatal("invalid token-id") }
  563. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  564. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  565. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  566. con := boundContract(addr, easyTokenDocumentABI, c)
  567. tx, err := con.Transact(auth, "setTokenURI", id, stuURI); if err != nil { log.Fatal(err) }
  568. fmt.Println(tx.Hash().Hex())
  569. }}
  570. cmd.Flags().StringVar(&stuID, "token-id", "", "Token ID")
  571. cmd.Flags().StringVar(&stuURI, "uri", "", "New URI")
  572. tokCmd.AddCommand(cmd)
  573. }
  574. // set-appraisal
  575. var sapID, sapVal string
  576. {
  577. cmd := &cobra.Command{Use: "set-appraisal", Short: "Set appraisal value (base units)", Run: func(cmd *cobra.Command, args []string) {
  578. loadEnv(); if sapID==""||sapVal=="" { log.Fatal("--token-id --value required") }
  579. id, ok := new(big.Int).SetString(sapID, 10); if !ok { log.Fatal("invalid token-id") }
  580. val, err := toWei(sapVal); if err != nil { log.Fatal(err) }
  581. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  582. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  583. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  584. con := boundContract(addr, easyTokenDocumentABI, c)
  585. tx, err := con.Transact(auth, "setAppraisal", id, val); if err != nil { log.Fatal(err) }
  586. fmt.Println(tx.Hash().Hex())
  587. }}
  588. cmd.Flags().StringVar(&sapID, "token-id", "", "Token ID")
  589. cmd.Flags().StringVar(&sapVal, "value", "", "Appraisal in base units")
  590. tokCmd.AddCommand(cmd)
  591. }
  592. // safe-mint
  593. var smTo, smURI, smHash, smVal string
  594. {
  595. cmd := &cobra.Command{Use: "safe-mint", Short: "Mint new document NFT", Run: func(cmd *cobra.Command, args []string) {
  596. loadEnv(); if smTo==""||smURI==""||smHash==""||smVal=="" { log.Fatal("--to --uri --hash --value required") }
  597. h, err := fromHex32(smHash); if err != nil { log.Fatal(err) }
  598. v, err := toWei(smVal); if err != nil { log.Fatal(err) }
  599. ctx, cancel := context.WithTimeout(context.Background(), 240*time.Second); defer cancel()
  600. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  601. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  602. con := boundContract(addr, easyTokenDocumentABI, c)
  603. tx, err := con.Transact(auth, "safeMint", common.HexToAddress(smTo), smURI, h, v); if err != nil { log.Fatal(err) }
  604. fmt.Println(tx.Hash().Hex())
  605. }}
  606. cmd.Flags().StringVar(&smTo, "to", "", "Recipient address")
  607. cmd.Flags().StringVar(&smURI, "uri", "", "Document URL")
  608. cmd.Flags().StringVar(&smHash, "hash", "", "Document hash (0x + 64 hex)")
  609. cmd.Flags().StringVar(&smVal, "value", "", "Appraisal in base units")
  610. tokCmd.AddCommand(cmd)
  611. }
  612. // transfer & safe-transfer
  613. var ttTo, ttID string
  614. {
  615. cmd := &cobra.Command{Use: "transfer", Short: "transferFrom(sender,to,tokenId)", Run: func(cmd *cobra.Command, args []string) {
  616. loadEnv(); if ttTo==""||ttID=="" { log.Fatal("--to --token-id required") }
  617. id, ok := new(big.Int).SetString(ttID, 10); if !ok { log.Fatal("invalid token-id") }
  618. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  619. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  620. sender := crypto.PubkeyToAddress(mustPrivKey().PublicKey)
  621. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  622. con := boundContract(addr, easyTokenDocumentABI, c)
  623. tx, err := con.Transact(auth, "safeTransferFrom", sender, common.HexToAddress(ttTo), id); if err != nil { log.Fatal(err) }
  624. fmt.Println(tx.Hash().Hex())
  625. }}
  626. cmd.Flags().StringVar(&ttTo, "to", "", "Recipient address")
  627. cmd.Flags().StringVar(&ttID, "token-id", "", "Token ID")
  628. tokCmd.AddCommand(cmd)
  629. }
  630. var stTo, stID string
  631. {
  632. cmd := &cobra.Command{Use: "safe-transfer", Short: "safeTransferFrom(sender,to,tokenId)", Run: func(cmd *cobra.Command, args []string) {
  633. loadEnv(); if stTo==""||stID=="" { log.Fatal("--to --token-id required") }
  634. id, ok := new(big.Int).SetString(stID, 10); if !ok { log.Fatal("invalid token-id") }
  635. ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second); defer cancel()
  636. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  637. sender := crypto.PubkeyToAddress(mustPrivKey().PublicKey)
  638. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  639. con := boundContract(addr, easyTokenDocumentABI, c)
  640. tx, err := con.Transact(auth, "safeTransferFrom", sender, common.HexToAddress(stTo), id); if err != nil { log.Fatal(err) }
  641. fmt.Println(tx.Hash().Hex())
  642. }}
  643. cmd.Flags().StringVar(&stTo, "to", "", "Recipient address")
  644. cmd.Flags().StringVar(&stID, "token-id", "", "Token ID")
  645. tokCmd.AddCommand(cmd)
  646. }
  647. // approvals
  648. var apTo string; var apID2 string
  649. {
  650. cmd := &cobra.Command{Use: "approve", Short: "Approve address for tokenId", Run: func(cmd *cobra.Command, args []string) {
  651. loadEnv(); if apTo==""||apID2=="" { log.Fatal("--to --token-id required") }
  652. id, ok := new(big.Int).SetString(apID2, 10); if !ok { log.Fatal("invalid token-id") }
  653. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  654. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  655. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  656. con := boundContract(addr, easyTokenDocumentABI, c)
  657. tx, err := con.Transact(auth, "approve", common.HexToAddress(apTo), id); if err != nil { log.Fatal(err) }
  658. fmt.Println(tx.Hash().Hex())
  659. }}
  660. cmd.Flags().StringVar(&apTo, "to", "", "Approved address")
  661. cmd.Flags().StringVar(&apID2, "token-id", "", "Token ID")
  662. tokCmd.AddCommand(cmd)
  663. }
  664. var saoOp string; var saoApproved bool
  665. {
  666. cmd := &cobra.Command{Use: "set-approval-for-all", Short: "Set operator approval", Run: func(cmd *cobra.Command, args []string) {
  667. loadEnv(); if saoOp=="" { log.Fatal("--operator required") }
  668. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  669. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  670. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  671. con := boundContract(addr, easyTokenDocumentABI, c)
  672. tx, err := con.Transact(auth, "setApprovalForAll", common.HexToAddress(saoOp), saoApproved); if err != nil { log.Fatal(err) }
  673. fmt.Println(tx.Hash().Hex())
  674. }}
  675. cmd.Flags().StringVar(&saoOp, "operator", "", "Operator address")
  676. cmd.Flags().BoolVar(&saoApproved, "approved", false, "Approved status")
  677. tokCmd.AddCommand(cmd)
  678. }
  679. // getters for approvals
  680. var gaID string
  681. {
  682. cmd := &cobra.Command{Use: "get-approved", Short: "Get approved for tokenId", Run: func(cmd *cobra.Command, args []string) {
  683. loadEnv(); if gaID=="" { log.Fatal("--token-id required") }
  684. id, ok := new(big.Int).SetString(gaID, 10); if !ok { log.Fatal("invalid token-id") }
  685. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  686. c, _ := mustClient(ctx)
  687. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  688. con := boundContract(addr, easyTokenDocumentABI, c)
  689. a, err := callAddress(ctx, con, "getApproved", id); if err != nil { log.Fatal(err) }
  690. fmt.Println(a.Hex())
  691. }}
  692. cmd.Flags().StringVar(&gaID, "token-id", "", "Token ID")
  693. tokCmd.AddCommand(cmd)
  694. }
  695. var iaOwner, iaOp string
  696. {
  697. cmd := &cobra.Command{Use: "is-approved-for-all", Short: "Is operator approved for owner", Run: func(cmd *cobra.Command, args []string) {
  698. loadEnv(); if iaOwner==""||iaOp=="" { log.Fatal("--owner --operator required") }
  699. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  700. c, _ := mustClient(ctx)
  701. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  702. con := boundContract(addr, easyTokenDocumentABI, c)
  703. v, err := callBool(ctx, con, "isApprovedForAll", common.HexToAddress(iaOwner), common.HexToAddress(iaOp)); if err != nil { log.Fatal(err) }
  704. fmt.Println(v)
  705. }}
  706. cmd.Flags().StringVar(&iaOwner, "owner", "", "Owner address")
  707. cmd.Flags().StringVar(&iaOp, "operator", "", "Operator address")
  708. tokCmd.AddCommand(cmd)
  709. }
  710. // pause/unpause/paused
  711. tokCmd.AddCommand(&cobra.Command{Use: "paused", Short: "Is paused?", Run: func(cmd *cobra.Command, args []string) {
  712. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  713. c, _ := mustClient(ctx)
  714. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  715. con := boundContract(addr, easyTokenDocumentABI, c)
  716. p, err := callBool(ctx, con, "paused"); if err != nil { log.Fatal(err) }
  717. fmt.Println(p)
  718. }})
  719. tokCmd.AddCommand(&cobra.Command{Use: "pause", Short: "Pause transfers", Run: func(cmd *cobra.Command, args []string) {
  720. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  721. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  722. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  723. con := boundContract(addr, easyTokenDocumentABI, c)
  724. tx, err := con.Transact(auth, "pause"); if err != nil { log.Fatal(err) }
  725. fmt.Println(tx.Hash().Hex())
  726. }})
  727. tokCmd.AddCommand(&cobra.Command{Use: "unpause", Short: "Unpause transfers", Run: func(cmd *cobra.Command, args []string) {
  728. loadEnv(); ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  729. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  730. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  731. con := boundContract(addr, easyTokenDocumentABI, c)
  732. tx, err := con.Transact(auth, "unpause"); if err != nil { log.Fatal(err) }
  733. fmt.Println(tx.Hash().Hex())
  734. }})
  735. // blacklist
  736. var tblAcc string; var tblStatus bool
  737. {
  738. cmd := &cobra.Command{Use: "set-blacklist", Short: "Set blacklist status", Run: func(cmd *cobra.Command, args []string) {
  739. loadEnv(); if tblAcc=="" { log.Fatal("--account required") }
  740. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  741. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  742. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  743. con := boundContract(addr, easyTokenDocumentABI, c)
  744. tx, err := con.Transact(auth, "setBlacklist", common.HexToAddress(tblAcc), tblStatus); if err != nil { log.Fatal(err) }
  745. fmt.Println(tx.Hash().Hex())
  746. }}
  747. cmd.Flags().StringVar(&tblAcc, "account", "", "Account")
  748. cmd.Flags().BoolVar(&tblStatus, "status", false, "Blacklist status")
  749. tokCmd.AddCommand(cmd)
  750. }
  751. var tqblAcc string
  752. {
  753. cmd := &cobra.Command{Use: "is-blacklisted", Short: "Check blacklist", Run: func(cmd *cobra.Command, args []string) {
  754. loadEnv(); if tqblAcc=="" { log.Fatal("--account required") }
  755. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  756. c, _ := mustClient(ctx)
  757. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  758. con := boundContract(addr, easyTokenDocumentABI, c)
  759. v, err := callBool(ctx, con, "isBlacklisted", common.HexToAddress(tqblAcc)); if err != nil { log.Fatal(err) }
  760. fmt.Println(v)
  761. }}
  762. cmd.Flags().StringVar(&tqblAcc, "account", "", "Account")
  763. tokCmd.AddCommand(cmd)
  764. }
  765. // roles
  766. var trRole, trAcc string
  767. {
  768. cmd := &cobra.Command{Use: "grant-role", Short: "Grant role", Run: func(cmd *cobra.Command, args []string) {
  769. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  770. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  771. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  772. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  773. con := boundContract(addr, easyTokenDocumentABI, c)
  774. role := roleID(trRole)
  775. tx, err := con.Transact(auth, "grantRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  776. fmt.Println(tx.Hash().Hex())
  777. }}
  778. cmd.Flags().StringVar(&trRole, "role", "", "Role name (DEFAULT_ADMIN_ROLE, PAUSER_ROLE, MINTER_ROLE, METADATA_ROLE, APPRAISER_ROLE, COMPLIANCE_ROLE)")
  779. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  780. tokCmd.AddCommand(cmd)
  781. }
  782. trRole, trAcc = "", ""
  783. {
  784. cmd := &cobra.Command{Use: "revoke-role", Short: "Revoke role", Run: func(cmd *cobra.Command, args []string) {
  785. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  786. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  787. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  788. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  789. con := boundContract(addr, easyTokenDocumentABI, c)
  790. role := roleID(trRole)
  791. tx, err := con.Transact(auth, "revokeRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  792. fmt.Println(tx.Hash().Hex())
  793. }}
  794. cmd.Flags().StringVar(&trRole, "role", "", "Role name")
  795. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  796. tokCmd.AddCommand(cmd)
  797. }
  798. trRole, trAcc = "", ""
  799. {
  800. cmd := &cobra.Command{Use: "has-role", Short: "Has role?", Run: func(cmd *cobra.Command, args []string) {
  801. loadEnv(); if trRole==""||trAcc=="" { log.Fatal("--role --account required") }
  802. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel()
  803. c, _ := mustClient(ctx)
  804. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  805. con := boundContract(addr, easyTokenDocumentABI, c)
  806. role := roleID(trRole)
  807. v, err := callBool(ctx, con, "hasRole", role, common.HexToAddress(trAcc)); if err != nil { log.Fatal(err) }
  808. fmt.Println(v)
  809. }}
  810. cmd.Flags().StringVar(&trRole, "role", "", "Role name")
  811. cmd.Flags().StringVar(&trAcc, "account", "", "Account address")
  812. tokCmd.AddCommand(cmd)
  813. }
  814. var tgcAcc string
  815. {
  816. cmd := &cobra.Command{Use: "grant-compliance-role", Short: "Grant COMPLIANCE_ROLE via helper", Run: func(cmd *cobra.Command, args []string) {
  817. loadEnv(); if tgcAcc=="" { log.Fatal("--account required") }
  818. ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second); defer cancel()
  819. c, chain := mustClient(ctx); auth := txOpts(ctx, c, chain)
  820. addr := mustAddress(tokenAddr, "EASY_TOKEN_ADDR")
  821. con := boundContract(addr, easyTokenDocumentABI, c)
  822. tx, err := con.Transact(auth, "grantComplianceRole", common.HexToAddress(tgcAcc)); if err != nil { log.Fatal(err) }
  823. fmt.Println(tx.Hash().Hex())
  824. }}
  825. cmd.Flags().StringVar(&tgcAcc, "account", "", "Account address")
  826. tokCmd.AddCommand(cmd)
  827. }
  828. return tokCmd
  829. }
  830. func cmdPolygon() *cobra.Command {
  831. pg := &cobra.Command{Use: "polygon", Short: "Polygon utilities"}
  832. pg.AddCommand(&cobra.Command{Use: "create-new-address", Short: "Generate a new wallet", Run: func(cmd *cobra.Command, args []string) {
  833. pk, err := crypto.GenerateKey(); if err != nil { log.Fatal(err) }
  834. pkHex := "0x" + common.Bytes2Hex(crypto.FromECDSA(pk))
  835. pubHex := "0x" + common.Bytes2Hex(crypto.FromECDSAPub(&pk.PublicKey))
  836. addr := crypto.PubkeyToAddress(pk.PublicKey).Hex()
  837. fmt.Printf("privateKey=%s\npublicKey=%s\naddress=%s\n", pkHex, pubHex, addr)
  838. }})
  839. return pg
  840. }
  841. func main() {
  842. root := &cobra.Command{Use: "sdk", Short: "CLI for EasyBRL (coin) and EasyToken (token)"}
  843. root.PersistentFlags().StringVar(&rpcURL, "rpc", "", "RPC URL (overrides .env)")
  844. root.PersistentFlags().StringVar(&pkHex, "pk", "", "Private key hex (overrides .env)")
  845. root.PersistentFlags().StringVar(&coinAddr, "coin-addr", coinAddr, "ERC20 contract address (overrides .env)")
  846. root.PersistentFlags().StringVar(&tokenAddr, "token-addr", tokenAddr, "ERC721 contract address (overrides .env)")
  847. root.AddCommand(cmdCoin())
  848. root.AddCommand(cmdToken())
  849. root.AddCommand(cmdPolygon())
  850. if err := root.Execute(); err != nil {
  851. os.Exit(1)
  852. }
  853. }