Compare commits

...

53 Commits

Author SHA1 Message Date
dependabot[bot] 8b116f8a0f
build(deps): bump github.com/redis/go-redis/v9 from 9.0.4 to 9.0.5 (#95)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.4 to 9.0.5.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.4...v9.0.5)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:35:49 +08:00
dependabot[bot] 888fb4ce11
build(deps): bump github.com/go-playground/validator/v10 (#96)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.13.0 to 10.14.1.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.13.0...v10.14.1)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:34:43 +08:00
dependabot[bot] 27b300db93
build(deps): bump github.com/nats-io/nats.go from 1.25.0 to 1.27.1 (#99)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.25.0 to 1.27.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.25.0...v1.27.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:52 +08:00
dependabot[bot] 788cf66fc7
build(deps): bump github.com/VictoriaMetrics/metrics (#93)
Bumps [github.com/VictoriaMetrics/metrics](https://github.com/VictoriaMetrics/metrics) from 1.23.1 to 1.24.0.
- [Commits](https://github.com/VictoriaMetrics/metrics/compare/v1.23.1...v1.24.0)

---
updated-dependencies:
- dependency-name: github.com/VictoriaMetrics/metrics
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:19 +08:00
dependabot[bot] d2cb0796c8
build(deps): bump github.com/jackc/tern/v2 from 2.1.0 to 2.1.1 (#98)
Bumps [github.com/jackc/tern/v2](https://github.com/jackc/tern) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/jackc/tern/releases)
- [Changelog](https://github.com/jackc/tern/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/jackc/tern/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: github.com/jackc/tern/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:09 +08:00
Mohamed Sohail 9e1d62a014
feat: add transfer authorization (#100)
* wip: add transfer auithorization

* feat: update transferAuthPayload

* switch to MAX_INT for value and use revoke flag
* tranfer handler: fix nonce rollback on EOA account

* feat: switch to session based session transfer auth

* Demurrage contracts require setting the approval value to 0 before updating the value after the initial limit is set
* This implementation auto-revokes every 15 min after arequest is created. Subsequent requests will fail untill the value is set back to 0

* feat: settable approve session timeout
2023-07-10 14:32:05 +08:00
Mohamed Sohail 98ff897049
fix: tune gas params (celoutils v1.4.0) 2023-06-08 12:08:58 +08:00
Mohamed Sohail 8dc0dcf12d
fix: use celoutils.SafeGasLimit in place
* Gas limit is now 1M for all custodial related txs
* Precise value to be determined after testing phase is over
2023-05-28 20:43:43 +08:00
dependabot[bot] f84b90f411
build(deps): bump github.com/go-playground/validator/v10 (#86)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.12.0 to 10.13.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.12.0...v10.13.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:40 +03:00
dependabot[bot] 83113c8411
build(deps): bump github.com/swaggo/swag from 1.8.12 to 1.16.1 (#83)
Bumps [github.com/swaggo/swag](https://github.com/swaggo/swag) from 1.8.12 to 1.16.1.
- [Release notes](https://github.com/swaggo/swag/releases)
- [Changelog](https://github.com/swaggo/swag/blob/master/.goreleaser.yml)
- [Commits](https://github.com/swaggo/swag/compare/v1.8.12...v1.16.1)

---
updated-dependencies:
- dependency-name: github.com/swaggo/swag
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:25 +03:00
dependabot[bot] 6ccc27685c
build(deps): bump github.com/jackc/tern/v2 from 2.0.1 to 2.1.0 (#82)
Bumps [github.com/jackc/tern/v2](https://github.com/jackc/tern) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/jackc/tern/releases)
- [Changelog](https://github.com/jackc/tern/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/jackc/tern/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: github.com/jackc/tern/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:10 +03:00
dependabot[bot] e821da13d4
build(deps): bump github.com/bsm/redislock from 0.9.2 to 0.9.3 (#87)
Bumps [github.com/bsm/redislock](https://github.com/bsm/redislock) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/bsm/redislock/releases)
- [Changelog](https://github.com/bsm/redislock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bsm/redislock/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: github.com/bsm/redislock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:22:56 +03:00
dependabot[bot] 2b42da206b
build(deps): bump github.com/redis/go-redis/v9 from 9.0.3 to 9.0.4 (#88)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.3 to 9.0.4.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.3...v9.0.4)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:22:38 +03:00
Mohamed Sohail b9d3c219c8
fix (braking change): gas refill params (#89)
NOTE: This needs the db to be nuked if you are running a test cluster

* updated gas refilling logic to reflect EthFaucet contract
* fully dependant on on-chain contract to refill and unlock gas
* minor fixes to nonce bootstrapper

Ideal Values:

INITIAL GIFT = 0.015
THRESHOLD    = 0.01
TIME = 12 * 60 * 60

# Sample for 30 txs
EXTREME LOW = 0.00675
LOW GAS USAGE = 0.00694605
RISING = 0.0135
HIGH = 0.027
2023-05-16 15:20:01 +03:00
Mohamed Sohail 8ef2311d8e
fix: export TxStatus struct 2023-04-14 08:05:05 +00:00
Mohamed Sohail e91f82c08a
docs: add godocs comments to all routes
* disable serving docs by default
2023-04-14 07:57:43 +00:00
dependabot[bot] 223656b2bc
build(deps): bump github.com/bsm/redislock from 0.9.1 to 0.9.2 (#80)
Bumps [github.com/bsm/redislock](https://github.com/bsm/redislock) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/bsm/redislock/releases)
- [Changelog](https://github.com/bsm/redislock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bsm/redislock/compare/v0.9.1...v0.9.2)

---
updated-dependencies:
- dependency-name: github.com/bsm/redislock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:19:56 +03:00
dependabot[bot] 0218831bd9
build(deps): bump github.com/knadh/koanf/v2 from 2.0.0 to 2.0.1 (#81)
Bumps [github.com/knadh/koanf/v2](https://github.com/knadh/koanf) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/knadh/koanf/releases)
- [Commits](https://github.com/knadh/koanf/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: github.com/knadh/koanf/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:48 +03:00
dependabot[bot] b2df62f0df
build(deps): bump golang.org/x/crypto from 0.7.0 to 0.8.0 (#79)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:30 +03:00
dependabot[bot] d9acaa9583
build(deps): bump github.com/redis/go-redis/v9 from 9.0.2 to 9.0.3 (#77)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.2 to 9.0.3.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.2...v9.0.3)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:19 +03:00
Mohamed Sohail d51e74883d
docs: add swagger auto generator
* to generate docs, run `make docs`
* endpoint /swagger/
2023-04-13 11:55:07 +00:00
Mohamed Sohail 0e5db7f06f
feat: add nonce bootstrapper
Useful for rebuilding the nonce cache automatically.

* uses otx_sign as the first source
* can fallback to chain nonce value of the 1st source is corrupted
2023-04-13 10:38:23 +00:00
Mohamed Sohail b137088d38
sub: update handlers for latest CHAIN.* events
* Minor fixes and improvements
2023-04-11 10:18:06 +00:00
Mohamed Sohail 21a17d2735
feat: add SLA package which defines a global ctx timeout 2023-04-11 10:15:33 +00:00
Mohamed Sohail eba329eefa
refactor (store): consolidate all pg store related actions
* All postgres related functions now live in internal/store.
* Updated queries.sql file to match struct order (readibility)
* Moved keystore -> store
* Moved queries -> store
* Removed pkg/postgres
2023-04-11 10:14:49 +00:00
Mohamed Sohail 82294b96f8
refactor: pgx supports big.Int type conversion internally 2023-04-06 06:31:07 +00:00
Mohamed Sohail e67a42ede3
fix: config.toml and dev docker-compose 2023-04-06 06:12:37 +00:00
Mohamed Sohail a98fe958a3
refactor: remove unecessary publisher component
* clients should now rely on the CHAIn.* subjects
2023-04-06 06:08:38 +00:00
Mohamed Sohail e203c49049
major refactor: use proxy contract and gas faucet (see notes)
* remove uncessary tasks and task handlers
* reafctor custodial container
* refactor gas refiller. Gas refiller can queue at a later time to match cooldown
* refactor sub handler to process chain events
2023-03-29 16:10:58 +00:00
Mohamed Sohail 448b142f7c
refactor: nonce store interface
* A nonce value can  now be bootstrapped externally from any source
* Removed unecessary SetNewAccountNonce method
2023-03-29 07:37:59 +00:00
Mohamed Sohail 750fdd9c1f
fix: global lock middleware func signature 2023-03-28 06:55:51 +00:00
Mohamed Sohail 5f5678e25b
refactor: use echo ctx only for cancellation/propagation 2023-03-28 06:39:44 +00:00
Mohamed Sohail ef5bd1860f
deps(go-playground-validator): use inbuilt eth_addr_checksum validator on routes 2023-03-28 06:30:29 +00:00
Mohamed Sohail ac5567bc56
deps: update 2023-03-28 06:27:15 +00:00
Mohamed Sohail 2d5e41eb81
chore: cleanup unecessary checksum fn 2023-03-28 06:27:15 +00:00
dependabot[bot] fc49f2387d
build(deps): bump github.com/go-playground/validator/v10 (#72)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.2 to 10.12.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.11.2...v10.12.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 09:26:35 +03:00
dependabot[bot] 81a3bb7e3c
build(deps): bump github.com/nats-io/nats.go from 1.24.0 to 1.25.0 (#73)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 09:14:10 +03:00
Mohamed Sohail 0235f25f81
ci: fix outputs 2023-03-16 12:11:03 +00:00
Mohamed Sohail 04e5d3c20f
major: breaking changes, fixes
* add global lock
* fix lock contention
* update redis client and server -> v7
* add network status API
* upgrade deps

Squashed commit of the following:

commit 9d95f2e8f8
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Thu Mar 16 11:12:06 2023 +0000

    feat: add updated_at postgres fn + trigger

    * closes #66

commit 144d5018ea
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Thu Mar 16 08:48:45 2023 +0000

    feat: add network account status (nonce, balance)

commit 5679a675f3
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Thu Mar 16 08:01:58 2023 +0000

    fix: system global lock

    * add middleware to entire API group
    * setNX system lock key

commit ee907dddbc
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Thu Mar 16 07:34:29 2023 +0000

    feat: add system global lock to be triggered manually

commit ad58d1da47
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Tue Mar 14 16:02:25 2023 +0000

    feat: add lock retry strategy

    * previouly we relied on the task being re-queued which generally reduces the throughput of tasks

commit f4e3aedf33
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Tue Mar 14 15:45:06 2023 +0000

    tasker: add support for reporting panics

commit b8ebf88f36
Author: Mohammed Sohail <sohailsameja@gmail.com>
Date:   Tue Mar 14 15:40:50 2023 +0000

    pkg: bump go-redis -> v9

commit 4a0bf88322
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Mar 14 18:15:05 2023 +0300

    build(deps): bump github.com/jackc/tern/v2 from 2.0.0 to 2.0.1 (#69)

    Bumps [github.com/jackc/tern/v2](https://github.com/jackc/tern) from 2.0.0 to 2.0.1.
    - [Release notes](https://github.com/jackc/tern/releases)
    - [Changelog](https://github.com/jackc/tern/blob/master/.goreleaser.yaml)
    - [Commits](https://github.com/jackc/tern/compare/v2.0.0...v2.0.1)

    ---
    updated-dependencies:
    - dependency-name: github.com/jackc/tern/v2
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 5328d271c1
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Mar 14 18:14:45 2023 +0300

    build(deps): bump golang.org/x/crypto from 0.6.0 to 0.7.0 (#70)

    Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0.
    - [Release notes](https://github.com/golang/crypto/releases)
    - [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.0)

    ---
    updated-dependencies:
    - dependency-name: golang.org/x/crypto
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 7ce80f9e6d
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Mar 14 18:13:59 2023 +0300

    build(deps): bump github.com/grassrootseconomics/celoutils (#71)

    Bumps [github.com/grassrootseconomics/celoutils](https://github.com/grassrootseconomics/celoutils) from 1.0.0 to 1.1.1.
    - [Release notes](https://github.com/grassrootseconomics/celoutils/releases)
    - [Commits](https://github.com/grassrootseconomics/celoutils/compare/v1.0.0...v1.1.1)

    ---
    updated-dependencies:
    - dependency-name: github.com/grassrootseconomics/celoutils
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 12:03:51 +00:00
Mohamed Sohail ea53214e2b
fix: docker default host bind 2023-03-09 08:25:11 +00:00
Mohamed Sohail ee07e61457
fix: README title 2023-03-08 15:01:13 +00:00
Mohamed Sohail fe9d217b60
docs: add README, add Makefile
dev: add Makefile

docs: add README

fix: Makefile
2023-03-08 14:58:18 +00:00
Mohamed Sohail bbd4b57fa8
pkg: fix missing deps 2023-03-08 12:09:34 +00:00
Mohamed Sohail b1572cfba7
chore: cleanup repo 2023-03-08 10:13:11 +00:00
Mohamed Sohail 0fd35f9668
build: update ci build stages 2023-03-08 10:02:56 +00:00
Mohamed Sohail 5a94a38805
fix: deepsource suggestions 2023-03-08 06:59:10 +00:00
Mohamed Sohail 0ef18b4e3e
fix: ununsed logg param 2023-03-08 06:51:48 +00:00
Mohamed Sohail 341a760f02
feat: (wip) add account activation and gas quota lock
* This is a crude lock that restricts each account to the set gas quota.
2023-03-08 06:49:09 +00:00
Mohamed Sohail ec14328d49
hotfix: gift gas correct enum for otx type 2023-03-06 10:06:49 +00:00
Mohamed Sohail 70419a9b19
refactor: (breaking) decouple js pub/sub, improve tasker server 2023-03-06 08:18:41 +00:00
Mohamed Sohail a47e44f262
refactor: use sigChan for shutdown, ctx fixes
* refactor main entry point for starting services
* minor fixes around ctx propagation
* improve otx marker js subscriber
2023-03-02 15:46:02 +00:00
Mohamed Sohail a1b6cb08d8
add: wip jetstream durable consumer 2023-03-02 09:12:39 +00:00
Mohamed Sohail 1ddff06502
feat (wip): add in-built chain subscription
* BREAKING: remove events interface -> use jetstream struct
* in-built chain subscription to update otx status
* potential fix for tasks: defer rollback nonce error detection
2023-03-01 17:13:23 +00:00
75 changed files with 3070 additions and 1812 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
.github
.deepsource.toml
.goreleaser.yaml
**/.env
**/.git
**/.gitignore
**/docker-compose*
**/Dockerfile*

View File

@ -1,3 +1,10 @@
POSTGRES_DSN=postgres://postgres:postgres@postgres:5432/cic_custodial
REDIS_DSN=redis://redis:6379/1
ASYNQ_DSN=redis://redis:6379/0
CUSTODIAL_CHAIN__RPC_ENDPOINT=
CUSTODIAL_SYSTEM__GAS_FAUCET_ADDRESS=
CUSTODIAL_SYSTEM__GIFTABLE_TOKEN_ADDRESS=
CUSTODIAL_SYSTEM__PRIVATE_KEY=
CUSTODIAL_SYSTEM__PUBLIC_KEY=
CUSTODIAL_SYSTEM__ACCOUNT_INDEX_ADDRESS=
CUSTODIAL_POSTGRES__DSN=
CUSTODIAL_REDIS__DSN=
CUSTODIAL_ASYNQ__DSN=
CUSTODIAL_JETSTREAM__ENDPOINT=

View File

@ -1,37 +0,0 @@
name: binary_release
on:
push:
tags:
- "v*"
jobs:
goreleaser:
runs-on: ubuntu-latest
container:
image: goreleaser/goreleaser-cross
environment: build
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Workaround Git Security Warning
run: |
# Workaround a bug in github actions:
# https://github.com/actions/runner-images/issues/6775.
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.20.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,29 +0,0 @@
name: docker_release
on:
push:
tags:
- "v*"
jobs:
build-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/login-action@v1.14.1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: docker compose build push
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
run: |
export TAG=$GITHUB_REF_NAME
docker-compose -f docker-compose.build.yaml build --progress plain
docker-compose -f docker-compose.build.yaml push
export TAG=latest
docker-compose -f docker-compose.build.yaml build --progress plain
docker-compose -f docker-compose.build.yaml push

100
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,100 @@
name: release
on:
push:
tags:
- 'v*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Check out repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GHCR Docker registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set outputs
run: |
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build and push image
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64
push: true
build-args: |
BUILD_COMMIT=${{ env.RELEASE_SHORT_COMMIT }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
tags: |
ghcr.io/grassrootseconomics/cic-custodial/cic-custodial:latest
ghcr.io/grassrootseconomics/cic-custodial/cic-custodial:${{ env.RELEASE_TAG }}
goreleaser:
runs-on: ubuntu-latest
container:
image: goreleaser/goreleaser-cross
environment: build
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Workaround Git Security Warning
run: |
# Workaround a bug in github actions:
# https://github.com/actions/runner-images/issues/6775.
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 'stable'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
cic-custodial
dist
.env
**/*.env
covprofile

View File

@ -10,7 +10,7 @@ builds:
- amd64
main: ./cmd/service
ldflags:
- -s -w
- -X main.build={{.ShortCommit}} -s -w
archives:
- format: tar.gz

View File

@ -1,20 +1,33 @@
# build
FROM golang:1-bullseye as build
WORKDIR /build
COPY . .
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o cic-custodial -ldflags="-s -w" cmd/service/*.go
# main
ENV CGO_ENABLED=1
ENV GOOS=linux
ENV GOARCH=amd64
ENV BUILD_COMMIT=${BUILD_COMMIT}
WORKDIR /build
COPY go.* .
RUN go mod download
COPY . .
RUN go build -o cic-custodial -ldflags="-X main.build=${BUILD_COMMIT} -s -w" cmd/service/*
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN set -x && \
apt-get update && \
apt-get install -y ca-certificates && \
rm -rf /var/cache/apt/archives /var/lib/apt/lists/*
WORKDIR /service
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /build/cic-custodial .
COPY migrations migrations/
COPY config.toml .
COPY queries.sql .
CMD ["./cic-custodial"]
COPY LICENSE .
EXPOSE 5000
CMD ["./cic-custodial"]

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
BIN := cic-custodial
BUILD_CONF := CGO_ENABLED=1 GOOS=linux GOARCH=amd64
BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null)
.PHONY: build run run-debug docs
clean:
rm ${BIN}
build:
${BUILD_CONF} go build -ldflags="-X main.build=${BUILD_COMMIT} -s -w" -o ${BIN} cmd/service/*
docs:
swag fmt --dir internal/api/
swag init --dir internal/api/ -g swagger.go
run:
${BUILD_CONF} go run cmd/service/*
run-debug:
${BUILD_CONF} go run cmd/service/* -debug

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# cic-custodial
![GitHub release (latest by date)](https://img.shields.io/github/v/release/grassrootseconomics/cic-custodial)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/grassrootseconomics/cic-custodial/release.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/grassrootseconomics/cic-custodial)](https://goreportcard.com/report/github.com/grassrootseconomics/cic-custodial)
> CIC Custodial
Experimental custodial API.
## Documentation
- [Usage](docs/usage.md)
## License
[AGPL-3.0](LICENSE).

View File

@ -2,44 +2,37 @@ package main
import (
"net/http"
"time"
"github.com/VictoriaMetrics/metrics"
"github.com/go-playground/validator/v10"
_ "github.com/grassrootseconomics/cic-custodial/docs"
"github.com/grassrootseconomics/cic-custodial/internal/api"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
echoSwagger "github.com/swaggo/echo-swagger"
)
const (
contextTimeout = 5
systemGlobalLockKey = "system:global_lock"
)
// Bootstrap API server.
func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
customValidator := validator.New()
customValidator.RegisterValidation("eth_checksum", api.EthChecksumValidator)
server := echo.New()
server.HideBanner = true
server.HidePort = true
server.Validator = &api.Validator{
ValidatorProvider: customValidator,
}
server.HTTPErrorHandler = customHTTPErrorHandler
server.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set("cu", custodialContainer)
return next(c)
}
})
server.Use(middleware.Recover())
server.Use(middleware.BodyLimit("1M"))
server.Use(middleware.ContextTimeout(time.Duration(contextTimeout * time.Second)))
server.Use(middleware.ContextTimeout(util.SLATimeout))
if ko.Bool("service.metrics") {
server.GET("/metrics", func(c echo.Context) error {
@ -48,10 +41,17 @@ func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
})
}
apiRoute := server.Group("/api")
apiRoute.POST("/account/create", api.HandleAccountCreate)
apiRoute.POST("/sign/transfer", api.HandleSignTransfer)
apiRoute.GET("/track/:trackingId", api.HandleTrackTx)
if ko.Bool("service.docs") {
server.GET("/docs/*", echoSwagger.WrapHandler)
}
apiRoute := server.Group("/api", systemGlobalLock(custodialContainer))
apiRoute.POST("/account/create", api.HandleAccountCreate(custodialContainer))
apiRoute.GET("/account/status/:address", api.HandleNetworkAccountStatus(custodialContainer))
apiRoute.POST("/sign/transfer", api.HandleSignTransfer(custodialContainer))
apiRoute.POST("/sign/transferAuth", api.HandleSignTranserAuthorization(custodialContainer))
apiRoute.GET("/track/:trackingId", api.HandleTrackTx(custodialContainer))
return server
}
@ -61,8 +61,7 @@ func customHTTPErrorHandler(err error, c echo.Context) {
return
}
he, ok := err.(*echo.HTTPError)
if ok {
if he, ok := err.(*echo.HTTPError); ok {
var errorMsg string
if m, ok := he.Message.(error); ok {
@ -75,12 +74,32 @@ func customHTTPErrorHandler(err error, c echo.Context) {
Ok: false,
Message: errorMsg,
})
} else {
lo.Error("api: echo error", "path", c.Path(), "err", err)
return
}
c.JSON(http.StatusInternalServerError, api.ErrResp{
Ok: false,
Message: "Internal server error.",
})
lo.Error("api: echo error", "path", c.Path(), "err", err)
c.JSON(http.StatusInternalServerError, api.ErrResp{
Ok: false,
Message: "Internal server error.",
})
}
func systemGlobalLock(cu *custodial.Custodial) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
locked, err := cu.RedisClient.Get(c.Request().Context(), systemGlobalLockKey).Bool()
if err != nil {
return err
}
if locked {
return c.JSON(http.StatusServiceUnavailable, api.ErrResp{
Ok: false,
Message: "System manually locked.",
})
}
return next(c)
}
}
}

View File

@ -1,70 +0,0 @@
package main
import (
"context"
"math/big"
"time"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/go-redis/redis/v8"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/w3-celo-patch"
)
// Define common smart contrcat ABI's that can be injected into the system container.
// Any relevant function signature that will be used by the custodial system can be defined here.
func initAbis() map[string]*w3.Func {
return map[string]*w3.Func{
// Keccak hash -> 0x449a52f8
"mintTo": w3.MustNewFunc("mintTo(address, uint256)", "bool"),
// Keccak hash -> 0xa9059cbb
"transfer": w3.MustNewFunc("transfer(address,uint256)", "bool"),
// Keccak hash -> 0x23b872dd
"transferFrom": w3.MustNewFunc("transferFrom(address, address, uint256)", "bool"),
// Add to account index
"add": w3.MustNewFunc("add(address)", "bool"),
// giveTo gas refill
"giveTo": w3.MustNewFunc("giveTo(address)", "uint256"),
}
}
// Bootstrap the internal custodial system configs and system signer key.
// This container is passed down to individual tasker and API handlers.
func initSystemContainer(ctx context.Context, noncestore nonce.Noncestore) (*tasker.SystemContainer, error) {
// Some custodial system defaults loaded from the config file.
systemContainer := &tasker.SystemContainer{
Abis: initAbis(),
AccountIndexContract: w3.A(ko.MustString("system.account_index_address")),
GasFaucetContract: w3.A(ko.MustString("system.gas_faucet_address")),
GasRefillThreshold: big.NewInt(ko.MustInt64("system.gas_refill_threshold")),
GasRefillValue: big.NewInt(ko.MustInt64("system.gas_refill_value")),
GiftableGasValue: big.NewInt(ko.MustInt64("system.giftable_gas_value")),
GiftableToken: w3.A(ko.MustString("system.giftable_token_address")),
GiftableTokenValue: big.NewInt(ko.MustInt64("system.giftable_token_value")),
LockPrefix: ko.MustString("system.lock_prefix"),
LockTimeout: 1 * time.Second,
PublicKey: ko.MustString("system.public_key"),
TokenDecimals: ko.MustInt("system.token_decimals"),
TokenTransferGasLimit: uint64(ko.MustInt64("system.token_transfer_gas_limit")),
}
// Check if system signer account nonce is present.
// If not (first boot), we bootstrap it from the network.
currentSystemNonce, err := noncestore.Peek(ctx, ko.MustString("system.public_key"))
lo.Info("custodial: loaded (noncestore) system nonce", "nonce", currentSystemNonce)
if err == redis.Nil {
nonce, err := noncestore.SyncNetworkNonce(ctx, ko.MustString("system.public_key"))
lo.Info("custodial: syncing system nonce", "nonce", nonce)
if err != nil {
return nil, err
}
}
loadedPrivateKey, err := eth_crypto.HexToECDSA(ko.MustString("system.private_key"))
if err != nil {
return nil, err
}
systemContainer.PrivateKey = loadedPrivateKey
return systemContainer, nil
}

View File

@ -1,34 +1,31 @@
package main
import (
"context"
"strings"
"time"
"github.com/bsm/redislock"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/sub"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/logg"
"github.com/grassrootseconomics/cic-custodial/pkg/postgres"
"github.com/grassrootseconomics/cic-custodial/pkg/redis"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/knadh/goyesql/v2"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/nats-io/nats.go"
"github.com/zerodha/logf"
)
// Load logger.
func initLogger(debug bool) logf.Logger {
func initLogger() logf.Logger {
loggOpts := logg.LoggOpts{}
if debug {
if debugFlag {
loggOpts.Color = true
loggOpts.Caller = true
loggOpts.Debug = true
@ -38,28 +35,32 @@ func initLogger(debug bool) logf.Logger {
}
// Load config file.
func initConfig(configFilePath string) *koanf.Koanf {
func initConfig() *koanf.Koanf {
var (
ko = koanf.New(".")
)
confFile := file.Provider(configFilePath)
confFile := file.Provider(confFlag)
if err := ko.Load(confFile, toml.Parser()); err != nil {
lo.Fatal("Could not load config file", "error", err)
lo.Fatal("init: could not load config file", "error", err)
}
if err := ko.Load(env.Provider("CUSTODIAL_", ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, "")), "_", ".", -1)
return strings.ReplaceAll(strings.ToLower(
strings.TrimPrefix(s, "CUSTODIAL_")), "__", ".")
}), nil); err != nil {
lo.Fatal("Could not override config from env vars", "error", err)
lo.Fatal("init: could not override config from env vars", "error", err)
}
if debugFlag {
ko.Print()
}
return ko
}
// Load Celo chain provider.
func initCeloProvider() (*celoutils.Provider, error) {
func initCeloProvider() *celoutils.Provider {
providerOpts := celoutils.ProviderOpts{
RpcEndpoint: ko.MustString("chain.rpc_endpoint"),
}
@ -72,87 +73,48 @@ func initCeloProvider() (*celoutils.Provider, error) {
provider, err := celoutils.NewProvider(providerOpts)
if err != nil {
return nil, err
lo.Fatal("init: critical error loading chain provider", "error", err)
}
return provider, nil
}
// Load postgres pool.
func initPostgresPool() (*pgxpool.Pool, error) {
poolOpts := postgres.PostgresPoolOpts{
DSN: ko.MustString("postgres.dsn"),
MigrationsFolderPath: migrationsFolderFlag,
}
pool, err := postgres.NewPostgresPool(poolOpts)
if err != nil {
return nil, err
}
return pool, nil
return provider
}
// Load separate redis connection for the tasker on a reserved db namespace.
func initAsynqRedisPool() (*redis.RedisPool, error) {
func initAsynqRedisPool() *redis.RedisPool {
poolOpts := redis.RedisPoolOpts{
DSN: ko.MustString("asynq.dsn"),
MinIdleConns: ko.MustInt("redis.min_idle_conn"),
}
pool, err := redis.NewRedisPool(poolOpts)
pool, err := redis.NewRedisPool(context.Background(), poolOpts)
if err != nil {
return nil, err
lo.Fatal("init: critical error connecting to asynq redis db", "error", err)
}
return pool, nil
return pool
}
// Common redis connection on a different db namespace from the takser.
func initCommonRedisPool() (*redis.RedisPool, error) {
func initCommonRedisPool() *redis.RedisPool {
poolOpts := redis.RedisPoolOpts{
DSN: ko.MustString("redis.dsn"),
MinIdleConns: ko.MustInt("redis.min_idle_conn"),
}
pool, err := redis.NewRedisPool(poolOpts)
pool, err := redis.NewRedisPool(context.Background(), poolOpts)
if err != nil {
return nil, err
lo.Fatal("init: critical error connecting to common redis db", "error", err)
}
return pool, nil
}
// Load SQL statements into struct.
func initQueries(queriesPath string) (*queries.Queries, error) {
parsedQueries, err := goyesql.ParseFile(queriesPath)
if err != nil {
return nil, err
}
loadedQueries, err := queries.LoadQueries(parsedQueries)
if err != nil {
return nil, err
}
return loadedQueries, nil
}
// Load postgres based keystore.
func initPostgresKeystore(postgresPool *pgxpool.Pool, queries *queries.Queries) (keystore.Keystore, error) {
keystore := keystore.NewPostgresKeytore(keystore.Opts{
PostgresPool: postgresPool,
Queries: queries,
})
return keystore, nil
return pool
}
// Load redis backed noncestore.
func initRedisNoncestore(redisPool *redis.RedisPool, celoProvider *celoutils.Provider) nonce.Noncestore {
func initRedisNoncestore(redisPool *redis.RedisPool, chainProvider *celoutils.Provider, store store.Store) nonce.Noncestore {
return nonce.NewRedisNoncestore(nonce.Opts{
RedisPool: redisPool,
CeloProvider: celoProvider,
ChainProvider: chainProvider,
RedisPool: redisPool,
Store: store,
})
}
@ -164,30 +126,50 @@ func initLockProvider(redisPool redislock.RedisClient) *redislock.Client {
// Load tasker client.
func initTaskerClient(redisPool *redis.RedisPool) *tasker.TaskerClient {
return tasker.NewTaskerClient(tasker.TaskerClientOpts{
RedisPool: redisPool,
TaskRetention: time.Duration(ko.MustInt64("asynq.task_retention_hrs")) * time.Hour,
RedisPool: redisPool,
})
}
// Load Postgres store
func initPostgresStore(postgresPool *pgxpool.Pool, queries *queries.Queries) store.Store {
return store.NewPostgresStore(store.Opts{
PostgresPool: postgresPool,
Queries: queries,
// Load Postgres store.
func initPgStore() store.Store {
store, err := store.NewPgStore(store.Opts{
DSN: ko.MustString("postgres.dsn"),
MigrationsFolderPath: migrationsFolderFlag,
QueriesFolderPath: queriesFlag,
})
}
// Init JetStream context for tasker events.
func initJetStream() (events.EventEmitter, error) {
jsEmitter, err := events.NewJetStreamEventEmitter(events.JetStreamOpts{
ServerUrl: ko.MustString("jetstream.endpoint"),
PersistDuration: time.Duration(ko.MustInt("jetstream.persist_duration_hrs")) * time.Hour,
DedupDuration: time.Duration(ko.MustInt("jetstream.dedup_duration_hrs")) * time.Hour,
})
if err != nil {
return nil, err
lo.Fatal("init: critical error loading Postgres store", "error", err)
}
return jsEmitter, nil
return store
}
// Init JetStream context for both pub/sub.
func initJetStream() (*nats.Conn, nats.JetStreamContext) {
natsConn, err := nats.Connect(ko.MustString("jetstream.endpoint"))
if err != nil {
lo.Fatal("init: critical error connecting to NATS", "error", err)
}
js, err := natsConn.JetStream()
if err != nil {
lo.Fatal("init: bad JetStream opts", "error", err)
}
return natsConn, js
}
func initSub(natsConn *nats.Conn, jsCtx nats.JetStreamContext, cu *custodial.Custodial) *sub.Sub {
sub, err := sub.NewSub(sub.SubOpts{
CustodialContainer: cu,
JsCtx: jsCtx,
Logg: lo,
NatsConn: natsConn,
})
if err != nil {
lo.Fatal("init: critical error bootstrapping sub", "error", err)
}
return sub
}

View File

@ -3,20 +3,26 @@ package main
import (
"context"
"flag"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/sub"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/knadh/koanf/v2"
"github.com/labstack/echo/v4"
"github.com/zerodha/logf"
)
type internalServicesContainer struct {
apiService *echo.Echo
jetstreamSub *sub.Sub
taskerService *tasker.TaskerServer
}
var (
build string
confFlag string
debugFlag bool
migrationsFolderFlag string
@ -28,88 +34,59 @@ var (
func init() {
flag.StringVar(&confFlag, "config", "config.toml", "Config file location")
flag.BoolVar(&debugFlag, "log", false, "Enable debug logging")
flag.BoolVar(&debugFlag, "debug", false, "Enable debug logging")
flag.StringVar(&migrationsFolderFlag, "migrations", "migrations/", "Migrations folder location")
flag.StringVar(&queriesFlag, "queries", "queries.sql", "Queries file location")
flag.Parse()
lo = initLogger(debugFlag)
ko = initConfig(confFlag)
lo = initLogger()
ko = initConfig()
}
func main() {
var (
tasker *tasker.TaskerServer
apiServer *echo.Echo
)
lo.Info("main: starting cic-custodial", "build", build)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
celoProvider := initCeloProvider()
asynqRedisPool := initAsynqRedisPool()
redisPool := initCommonRedisPool()
queries, err := initQueries(queriesFlag)
if err != nil {
lo.Fatal("main: critical error loading SQL queries", "error", err)
}
celoProvider, err := initCeloProvider()
if err != nil {
lo.Fatal("main: critical error loading chain provider", "error", err)
}
postgresPool, err := initPostgresPool()
if err != nil {
lo.Fatal("main: critical error connecting to postgres", "error", err)
}
asynqRedisPool, err := initAsynqRedisPool()
if err != nil {
lo.Fatal("main: critical error connecting to asynq redis db", "error", err)
}
redisPool, err := initCommonRedisPool()
if err != nil {
lo.Fatal("main: critical error connecting to common redis db", "error", err)
}
postgresKeystore, err := initPostgresKeystore(postgresPool, queries)
if err != nil {
lo.Fatal("main: critical error loading keystore")
}
jsEventEmitter, err := initJetStream()
if err != nil {
lo.Fatal("main: critical error loading jetstream event emitter")
}
pgStore := initPostgresStore(postgresPool, queries)
redisNoncestore := initRedisNoncestore(redisPool, celoProvider)
store := initPgStore()
redisNoncestore := initRedisNoncestore(redisPool, celoProvider, store)
lockProvider := initLockProvider(redisPool.Client)
taskerClient := initTaskerClient(asynqRedisPool)
systemContainer, err := initSystemContainer(context.Background(), redisNoncestore)
natsConn, jsCtx := initJetStream()
custodial, err := custodial.NewCustodial(custodial.Opts{
ApprovalTimeout: ko.MustDuration("system.approve_timeout"),
CeloProvider: celoProvider,
LockProvider: lockProvider,
Logg: lo,
Noncestore: redisNoncestore,
Store: store,
RedisClient: redisPool.Client,
RegistryAddress: ko.MustString("chain.registry_address"),
SystemPrivateKey: ko.MustString("system.private_key"),
SystemPublicKey: ko.MustString("system.public_key"),
TaskerClient: taskerClient,
})
if err != nil {
lo.Fatal("main: critical error bootstrapping system container", "error", err)
}
custodial := &custodial.Custodial{
CeloProvider: celoProvider,
EventEmitter: jsEventEmitter,
Keystore: postgresKeystore,
LockProvider: lockProvider,
Noncestore: redisNoncestore,
PgStore: pgStore,
SystemContainer: systemContainer,
TaskerClient: taskerClient,
lo.Fatal("main: crtical error loading custodial container", "error", err)
}
internalServices := &internalServicesContainer{}
wg := &sync.WaitGroup{}
apiServer = initApiServer(custodial)
signalCh, closeCh := createSigChannel()
defer closeCh()
internalServices.apiService = initApiServer(custodial)
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("main: starting API server")
if err := apiServer.Start(ko.MustString("service.address")); err != nil {
host := ko.MustString("service.address")
lo.Info("main: starting API server", "host", host)
if err := internalServices.apiService.Start(host); err != nil {
if strings.Contains(err.Error(), "Server closed") {
lo.Info("main: shutting down server")
} else {
@ -118,25 +95,28 @@ func main() {
}
}()
tasker = initTasker(custodial, asynqRedisPool)
internalServices.taskerService = initTasker(custodial, asynqRedisPool)
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("Starting tasker")
if err := tasker.Start(); err != nil {
lo.Info("main: starting tasker")
if err := internalServices.taskerService.Start(); err != nil {
lo.Fatal("main: could not start task server", "err", err)
}
}()
<-ctx.Done()
internalServices.jetstreamSub = initSub(natsConn, jsCtx, custodial)
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("main: starting jetstream sub")
if err := internalServices.jetstreamSub.Process(); err != nil {
lo.Fatal("main: error running jetstream sub", "err", err)
}
}()
lo.Info("main: stopping tasker")
tasker.Stop()
lo.Info("main: stopping api server")
if err := apiServer.Shutdown(ctx); err != nil {
lo.Error("Could not gracefully shutdown api server", "err", err)
}
lo.Info("main: graceful shutdown triggered", "signal", <-signalCh)
startGracefulShutdown(context.Background(), internalServices)
wg.Wait()
}

View File

@ -1,6 +1,10 @@
package main
import (
"context"
"time"
"github.com/bsm/redislock"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
@ -8,26 +12,70 @@ import (
"github.com/hibiken/asynq"
)
const (
fixedRetryCount = 25
fixedRetryPeriod = time.Second * 1
)
// Load tasker handlers, injecting any necessary handler dependencies from the system container.
func initTasker(custodialContainer *custodial.Custodial, redisPool *redis.RedisPool) *tasker.TaskerServer {
lo.Debug("Bootstrapping tasker")
taskerServerOpts := tasker.TaskerServerOpts{
Concurrency: ko.MustInt("asynq.worker_count"),
Logg: lo,
LogLevel: asynq.InfoLevel,
RedisPool: redisPool,
Concurrency: ko.MustInt("asynq.worker_count"),
IsFailureHandler: isFailureHandler,
Logg: lo,
LogLevel: asynq.InfoLevel,
RedisPool: redisPool,
RetryHandler: retryHandler,
}
taskerServer := tasker.NewTaskerServer(taskerServerOpts)
taskerServer.RegisterHandlers(tasker.AccountPrepareTask, task.AccountPrepare(custodialContainer))
taskerServer.RegisterMiddlewareStack([]asynq.MiddlewareFunc{
observibilityMiddleware(),
})
taskerServer.RegisterHandlers(tasker.AccountRegisterTask, task.AccountRegisterOnChainProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountGiftGasTask, task.AccountGiftGasProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountGiftVoucherTask, task.GiftVoucherProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountRefillGasTask, task.AccountRefillGasProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.SignTransferTask, task.SignTransfer(custodialContainer))
taskerServer.RegisterHandlers(tasker.SignTransferTaskAuth, task.SignTransferAuthorizationProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.DispatchTxTask, task.DispatchTx(custodialContainer))
return taskerServer
}
func isFailureHandler(err error) bool {
switch err {
// Ignore lock contention errors; retry until lock obtain.
case redislock.ErrNotObtained:
return false
default:
return true
}
}
func retryHandler(count int, err error, task *asynq.Task) time.Duration {
if count < fixedRetryCount {
return fixedRetryPeriod
} else {
return asynq.DefaultRetryDelayFunc(count, err, task)
}
}
func observibilityMiddleware() asynq.MiddlewareFunc {
return func(handler asynq.Handler) asynq.Handler {
return asynq.HandlerFunc(func(ctx context.Context, task *asynq.Task) error {
taskId, _ := asynq.GetTaskID(ctx)
err := handler.ProcessTask(ctx, task)
if err != nil && isFailureHandler(err) {
lo.Error("tasker: handler error", "err", err, "task_type", task.Type(), "task_id", taskId)
} else if asynq.IsPanicError(err) {
lo.Error("tasker: handler panic", "err", err, "task_type", task.Type(), "task_id", taskId)
} else {
lo.Info("tasker: process task", "task_type", task.Type(), "task_id", taskId)
}
return err
})
}
}

31
cmd/service/utils.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
)
func createSigChannel() (chan os.Signal, func()) {
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
return signalCh, func() {
close(signalCh)
}
}
func startGracefulShutdown(ctx context.Context, internalServices *internalServicesContainer) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
internalServices.jetstreamSub.Close()
if err := internalServices.apiService.Shutdown(ctx); err != nil {
lo.Fatal("Could not gracefully shutdown api server", "err", err)
}
internalServices.taskerService.Stop()
}

View File

@ -1,34 +1,19 @@
[service]
address = ":5005"
address = ":5000"
# Exposes Go process Prometheus metrics
# /metrics endpoint
metrics = true
docs = false
[chain]
rpc_endpoint = ""
testnet = true
rpc_endpoint = ""
testnet = true
registry_address = ""
[system]
# System default values
# Gas values are in wei with 18 d.p. precision unless otherwise stated
# Token values are in wei with 6 d.p. precision unless otherwise stated
# All addresses MUST be checksumed
account_index_address = ""
lock_prefix = "lock:"
gas_faucet_address = ""
gas_refill_threshold = 2500000000000000
gas_refill_value = 10000000000000000
giftable_gas_value = 10000000000000000
giftable_token_address = ""
giftable_token_value = 5000000
# System private key
# Should always be toped up
private_key = ""
public_key = ""
token_decimals = 6
token_transfer_gas_limit = 200000
public_key = ""
approve_timeout = "30m"
[postgres]
dsn = ""
@ -36,17 +21,15 @@ dsn = ""
[redis]
# Used for locks and the Noncestore
# Ideally use DB 1
dsn = ""
dsn = ""
min_idle_conn = 5
[asynq]
# Exclusively used by the asynq tasker
# Ideally use DB 0
dsn = ""
dsn = ""
task_retention_hrs = 24
worker_count = 15
worker_count = 15
[jetstream]
endpoint = ""
persist_duration_hrs = 48
dedup_duration_hrs = 6
endpoint = ""

View File

@ -1,7 +1,7 @@
version: "3.9"
services:
redis:
image: redis:6-alpine
image: redis:7-alpine
restart: unless-stopped
command: redis-server --save 60 1 --loglevel warning
volumes:
@ -20,9 +20,9 @@ services:
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_USER=postgres
- POSTGRES_DB=cic_custodial
volumes:
- cic-custodial-pg:/var/lib/postgresql/data
- ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
ports:
- "127.0.0.1:5432:5432"
healthcheck:
@ -37,8 +37,8 @@ services:
volumes:
- cic-custodial-nats:/nats/data
ports:
- "4222:4222"
- "8222:8222"
- "127.0.0.1:4222:4222"
- "127.0.0.1:8222:8222"
asynqmon:
image: hibiken/asynqmon
restart: unless-stopped
@ -49,6 +49,16 @@ services:
depends_on:
redis:
condition: service_healthy
cic-chain-events:
image: ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:latest
restart: unless-stopped
env_file:
- events.env
ports:
- '127.0.0.1:5001:5000'
depends_on:
postgres:
condition: service_healthy
volumes:
cic-custodial-pg:
driver: local

2
dev/init_db.sql Normal file
View File

@ -0,0 +1,2 @@
CREATE DATABASE cic_custodial;
CREATE DATABASE cic_chain_events;

View File

@ -1,10 +0,0 @@
version: '3.9'
services:
cic-custodial:
image: ${IMAGE_BASE_URL:-ghcr.io/grassrootseconomics/cic-custodial}/cic-custodial:${TAG:-latest}
build:
context: .
dockerfile: Dockerfile
cache_from:
- ${IMAGE_BASE_URL:-ghcr.io/grassrootseconomics/cic-custodial}/cic-custodial:latest

312
docs/docs.go Normal file
View File

@ -0,0 +1,312 @@
// Code generated by swaggo/swag. DO NOT EDIT.
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "https://grassecon.org/pages/terms-and-conditions.html",
"contact": {
"name": "API Support",
"url": "https://grassecon.org/pages/contact-us",
"email": "devops@grassecon.org"
},
"license": {
"name": "AGPL-3.0",
"url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/account/create": {
"post": {
"description": "Create a new custodial account.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Create a new custodial account.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/account/status/{address}": {
"get": {
"description": "Return network balance and nonce.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Get an address's network balance and nonce.",
"parameters": [
{
"type": "string",
"description": "Account Public Key",
"name": "address",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transfer": {
"post": {
"description": "Sign and dispatch a transfer request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch transfer request.",
"parameters": [
{
"description": "Sign Transfer Request",
"name": "signTransferRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transferAuth": {
"post": {
"description": "Sign and dispatch a transfer authorization (approve) request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch a transfer authorization (approve) request.",
"parameters": [
{
"description": "Sign Transfer Authorization (approve) Request",
"name": "signTransferAuthorzationRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"authorizedAddress": {
"type": "string"
},
"authorizer": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/track/{trackingId}": {
"get": {
"description": "Track an OTX (Origin transaction) status.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"track"
],
"summary": "Track an OTX (Origin transaction) status.",
"parameters": [
{
"type": "string",
"description": "Tracking Id",
"name": "trackingId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
}
},
"definitions": {
"api.ErrResp": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"ok": {
"type": "boolean"
}
}
},
"api.H": {
"type": "object",
"additionalProperties": {}
},
"api.OkResp": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"result": {
"$ref": "#/definitions/api.H"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/api",
Schemes: []string{},
Title: "CIC Custodial API",
Description: "Interact with CIC Custodial API",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

286
docs/swagger.json Normal file
View File

@ -0,0 +1,286 @@
{
"swagger": "2.0",
"info": {
"description": "Interact with CIC Custodial API",
"title": "CIC Custodial API",
"termsOfService": "https://grassecon.org/pages/terms-and-conditions.html",
"contact": {
"name": "API Support",
"url": "https://grassecon.org/pages/contact-us",
"email": "devops@grassecon.org"
},
"license": {
"name": "AGPL-3.0",
"url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
},
"version": "1.0"
},
"basePath": "/api",
"paths": {
"/account/create": {
"post": {
"description": "Create a new custodial account.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Create a new custodial account.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/account/status/{address}": {
"get": {
"description": "Return network balance and nonce.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Get an address's network balance and nonce.",
"parameters": [
{
"type": "string",
"description": "Account Public Key",
"name": "address",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transfer": {
"post": {
"description": "Sign and dispatch a transfer request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch transfer request.",
"parameters": [
{
"description": "Sign Transfer Request",
"name": "signTransferRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transferAuth": {
"post": {
"description": "Sign and dispatch a transfer authorization (approve) request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch a transfer authorization (approve) request.",
"parameters": [
{
"description": "Sign Transfer Authorization (approve) Request",
"name": "signTransferAuthorzationRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"authorizedAddress": {
"type": "string"
},
"authorizer": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/track/{trackingId}": {
"get": {
"description": "Track an OTX (Origin transaction) status.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"track"
],
"summary": "Track an OTX (Origin transaction) status.",
"parameters": [
{
"type": "string",
"description": "Tracking Id",
"name": "trackingId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
}
},
"definitions": {
"api.ErrResp": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"ok": {
"type": "boolean"
}
}
},
"api.H": {
"type": "object",
"additionalProperties": {}
},
"api.OkResp": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"result": {
"$ref": "#/definitions/api.H"
}
}
}
}
}

188
docs/swagger.yaml Normal file
View File

@ -0,0 +1,188 @@
basePath: /api
definitions:
api.ErrResp:
properties:
message:
type: string
ok:
type: boolean
type: object
api.H:
additionalProperties: {}
type: object
api.OkResp:
properties:
ok:
type: boolean
result:
$ref: '#/definitions/api.H'
type: object
info:
contact:
email: devops@grassecon.org
name: API Support
url: https://grassecon.org/pages/contact-us
description: Interact with CIC Custodial API
license:
name: AGPL-3.0
url: https://www.gnu.org/licenses/agpl-3.0.en.html
termsOfService: https://grassecon.org/pages/terms-and-conditions.html
title: CIC Custodial API
version: "1.0"
paths:
/account/create:
post:
consumes:
- '*/*'
description: Create a new custodial account.
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Create a new custodial account.
tags:
- account
/account/status/{address}:
get:
consumes:
- '*/*'
description: Return network balance and nonce.
parameters:
- description: Account Public Key
in: path
name: address
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Get an address's network balance and nonce.
tags:
- network
/sign/transfer:
post:
consumes:
- application/json
description: Sign and dispatch a transfer request.
parameters:
- description: Sign Transfer Request
in: body
name: signTransferRequest
required: true
schema:
properties:
amount:
type: integer
from:
type: string
to:
type: string
voucherAddress:
type: string
type: object
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Sign and dispatch transfer request.
tags:
- network
/sign/transferAuth:
post:
consumes:
- application/json
description: Sign and dispatch a transfer authorization (approve) request.
parameters:
- description: Sign Transfer Authorization (approve) Request
in: body
name: signTransferAuthorzationRequest
required: true
schema:
properties:
amount:
type: integer
authorizedAddress:
type: string
authorizer:
type: string
voucherAddress:
type: string
type: object
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Sign and dispatch a transfer authorization (approve) request.
tags:
- network
/track/{trackingId}:
get:
consumes:
- '*/*'
description: Track an OTX (Origin transaction) status.
parameters:
- description: Tracking Id
in: path
name: trackingId
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Track an OTX (Origin transaction) status.
tags:
- track
swagger: "2.0"

60
go.mod
View File

@ -3,31 +3,40 @@ module github.com/grassrootseconomics/cic-custodial
go 1.20
require (
github.com/VictoriaMetrics/metrics v1.23.1
github.com/bsm/redislock v0.7.2
github.com/VictoriaMetrics/metrics v1.24.0
github.com/bsm/redislock v0.9.3
github.com/celo-org/celo-blockchain v1.7.2
github.com/georgysavva/scany/v2 v2.0.0
github.com/go-playground/validator/v10 v10.11.2
github.com/go-redis/redis/v8 v8.11.5
github.com/go-playground/validator/v10 v10.14.1
github.com/google/uuid v1.3.0
github.com/grassrootseconomics/celoutils v1.0.0
github.com/grassrootseconomics/celoutils v1.4.0
github.com/grassrootseconomics/w3-celo-patch v0.2.0
github.com/hibiken/asynq v0.24.0
github.com/jackc/pgx/v5 v5.3.1
github.com/jackc/tern/v2 v2.0.0
github.com/jackc/pgx/v5 v5.4.0
github.com/jackc/tern/v2 v2.1.1
github.com/knadh/goyesql/v2 v2.2.0
github.com/knadh/koanf v1.5.0
github.com/knadh/koanf/v2 v2.0.0
github.com/knadh/koanf/parsers/toml v0.1.0
github.com/knadh/koanf/providers/env v0.1.0
github.com/knadh/koanf/providers/file v0.1.0
github.com/knadh/koanf/v2 v2.0.1
github.com/labstack/echo/v4 v4.10.2
github.com/nats-io/nats.go v1.24.0
github.com/nats-io/nats.go v1.27.1
github.com/redis/go-redis/v9 v9.0.5
github.com/swaggo/echo-swagger v1.4.0
github.com/swaggo/swag v1.16.1
github.com/zerodha/logf v0.5.5
)
replace github.com/hibiken/asynq => github.com/grassrootseconomics/asynq v0.25.0
require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/VictoriaMetrics/fastcache v1.12.0 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/celo-org/celo-bls-go v0.6.4 // indirect
@ -37,11 +46,16 @@ require (
github.com/celo-org/celo-bls-go-macos v0.6.3 // indirect
github.com/celo-org/celo-bls-go-other v0.6.3 // indirect
github.com/celo-org/celo-bls-go-windows v0.6.3 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-stack/stack v1.8.1 // indirect
@ -61,8 +75,12 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
@ -70,11 +88,12 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/nats-io/nats-server/v2 v2.9.14 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.24.1 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
@ -82,6 +101,7 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
@ -90,13 +110,15 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/histogram v1.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

343
go.sum
View File

@ -34,10 +34,11 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
@ -45,55 +46,43 @@ github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0=
github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc=
github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw=
github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4=
github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bsm/redislock v0.7.2 h1:jggqOio8JyX9FJBKIfjF3fTxAu/v7zC5mAID9LveqG4=
github.com/bsm/redislock v0.7.2/go.mod h1:kS2g0Yvlymc9Dz8V3iVYAtLAaSVruYbAFdYBDrmC5WU=
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/redislock v0.9.3 h1:osmvugkXGiLDEhzUPdM0EUtKpTEgLLuli4Ky2Z4vx38=
github.com/bsm/redislock v0.9.3/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
@ -133,21 +122,19 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -167,22 +154,19 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/docker v1.6.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
@ -193,32 +177,32 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@ -226,13 +210,10 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@ -246,13 +227,11 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -272,14 +251,11 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -287,7 +263,6 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@ -300,54 +275,20 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/grassrootseconomics/celoutils v1.0.0 h1:+pfiNZA+PJczmpN7RsZ/sr9PM0jKQQHxB6y/Ix3AFwA=
github.com/grassrootseconomics/celoutils v1.0.0/go.mod h1:Uo5YRy6AGLAHDZj9jaOI+AWoQ1H3L0v79728pPMkm9Q=
github.com/grassrootseconomics/asynq v0.25.0 h1:2zSz5YwNLu/oCTm/xfNixn86i9aw4zth9Dl0dc2kFEs=
github.com/grassrootseconomics/asynq v0.25.0/go.mod h1:pe2XOdK1eIbTgTmRFHIYl75lvVuTPJxZq2T9Ocz/+2s=
github.com/grassrootseconomics/celoutils v1.4.0 h1:AJNKiOpfnQqZ3kRxeUlhWH/zlDDjhtbs/OzAMb5zU4A=
github.com/grassrootseconomics/celoutils v1.4.0/go.mod h1:Uo5YRy6AGLAHDZj9jaOI+AWoQ1H3L0v79728pPMkm9Q=
github.com/grassrootseconomics/w3-celo-patch v0.2.0 h1:YqibbPzX0tQKmxU1nUGzThPKk/fiYeYZY6Aif3eyu8U=
github.com/grassrootseconomics/w3-celo-patch v0.2.0/go.mod h1:WhBXNzNIvHmS6B2hAeShs56oa9Azb4jQSrOMKuMdBWw=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff/go.mod h1:Feit0l8NcNO4g69XNjwvsR0LGcwMMfzI1TF253rOIlQ=
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hibiken/asynq v0.24.0 h1:r1CiSVYCy1vGq9REKGI/wdB2D5n/QmtzihYHHXOuBUs=
github.com/hibiken/asynq v0.24.0/go.mod h1:FVnRfUTm6gcoDkM/EjF4OIh5/06ergCPUO6pS2B2y+w=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
@ -381,12 +322,12 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.4.0 h1:BSr+GCm4N6QcgIwv0DyTFHK9ugfEFF9DzSbbzxOiXU0=
github.com/jackc/pgx/v5 v5.4.0/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/tern/v2 v2.0.0 h1:ZE3nUQdjGFljIB2ExOgh9/2snUBfzvbAlbP8jt92xI0=
github.com/jackc/tern/v2 v2.0.0/go.mod h1:4cpqN/grjWYeRWcKXah5YGoviJKJuoqNLoORKLumoG0=
github.com/jackc/tern/v2 v2.1.1 h1:qDo41wTtDHrTgkN7lhcoMQ6oiAWqiD8xKgslxyoKHNQ=
github.com/jackc/tern/v2 v2.1.1/go.mod h1:xnRalAguscgir18eW/wscn/QTEoWwFqrpW+5S+CREWM=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
@ -395,45 +336,48 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/goyesql/v2 v2.2.0 h1:DNQIzgITmMTXA+z+jDzbXCpgr7fGD6Hp0AJ7ZLEAem4=
github.com/knadh/goyesql/v2 v2.2.0/go.mod h1:is+wK/XQBukYK3DdKfpJRyDH9U/ZTMyX2u6DFijjRnI=
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/knadh/koanf/v2 v2.0.0 h1:XPQ5ilNnwnNaHrfQ1YpTVhUAjcGHnEKA+lRpipQv02Y=
github.com/knadh/koanf/v2 v2.0.0/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
@ -442,18 +386,19 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
@ -461,12 +406,9 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -480,20 +422,10 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -501,29 +433,25 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
github.com/nats-io/nats-server/v2 v2.9.14 h1:n2GscWVgXpA14vQSRP/MM1SGi4wyazR9l19/gWxqgXQ=
github.com/nats-io/nats-server/v2 v2.9.14/go.mod h1:40ZwFm4npKdFBhOdY7rkh3YyI1oI91FzLvlYyB7HfzM=
github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nats.go v1.27.1 h1:OuYnal9aKVSnOzLQIzf7554OXMCG7KbaTkCSBHRcSoo=
github.com/nats-io/nats.go v1.27.1/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
@ -531,25 +459,20 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
@ -561,45 +484,33 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
github.com/redis/go-redis/v9 v9.0.1/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -611,8 +522,6 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -622,10 +531,11 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -633,9 +543,21 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/swaggo/echo-swagger v1.4.0 h1:RCxLKySw1SceHLqnmc41pKyiIeE+OiD7NSI7FUOBlLo=
github.com/swaggo/echo-swagger v1.4.0/go.mod h1:Wh3VlwjZGZf/LH0s81tz916JokuPG7y/ZqaqnckYqoQ=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
@ -657,46 +579,34 @@ github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OL
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zerodha/logf v0.5.5 h1:AhxHlixHNYwhFjvlgTv6uO4VBKYKxx2I6SbHoHtWLBk=
github.com/zerodha/logf v0.5.5/go.mod h1:HWpfKsie+WFFpnUnUxelT6Z0FC6xu9+qt+oXNMPg6y8=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -718,16 +628,15 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -742,26 +651,20 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -775,24 +678,19 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -801,23 +699,16 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -825,34 +716,31 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -860,8 +748,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -885,7 +773,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -895,14 +782,11 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -927,7 +811,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -940,45 +823,32 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
@ -986,12 +856,12 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
@ -1006,4 +876,3 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -12,52 +12,56 @@ import (
"github.com/labstack/echo/v4"
)
// CreateAccountHandler route.
// POST: /api/account/create
// Returns the public key.
func HandleAccountCreate(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
)
// HandleAccountCreate godoc
// @Summary Create a new custodial account.
// @Description Create a new custodial account.
// @Tags account
// @Accept */*
// @Produce json
// @Success 200 {object} OkResp
// @Failure 500 {object} ErrResp
// @Router /account/create [post]
func HandleAccountCreate(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
generatedKeyPair, err := keypair.Generate()
if err != nil {
return err
}
generatedKeyPair, err := keypair.Generate()
if err != nil {
return err
id, err := cu.Store.WriteKeyPair(c.Request().Context(), generatedKeyPair)
if err != nil {
return err
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.AccountPayload{
PublicKey: generatedKeyPair.Public,
TrackingId: trackingId,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.AccountRegisterTask,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"publicKey": generatedKeyPair.Public,
"custodialId": id,
"trackingId": trackingId,
},
})
}
id, err := cu.Keystore.WriteKeyPair(c.Request().Context(), generatedKeyPair)
if err != nil {
return err
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.AccountPayload{
PublicKey: generatedKeyPair.Public,
TrackingId: trackingId,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.AccountPrepareTask,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"publicKey": generatedKeyPair.Public,
"custodialId": id,
"trackingId": trackingId,
},
})
}

64
internal/api/network.go Normal file
View File

@ -0,0 +1,64 @@
package api
import (
"fmt"
"math/big"
"net/http"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/labstack/echo/v4"
)
// HandleNetworkAccountStatus godoc
// @Summary Get an address's network balance and nonce.
// @Description Return network balance and nonce.
// @Tags network
// @Accept */*
// @Produce json
// @Param address path string true "Account Public Key"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /account/status/{address} [get]
func HandleNetworkAccountStatus(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
accountStatusRequest struct {
Address string `param:"address" validate:"required,eth_addr_checksum"`
}
networkBalance big.Int
networkNonce uint64
)
if err := c.Bind(&accountStatusRequest); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(accountStatusRequest); err != nil {
return err
}
if err := cu.CeloProvider.Client.CallCtx(
c.Request().Context(),
eth.Nonce(celoutils.HexToAddress(accountStatusRequest.Address), nil).Returns(&networkNonce),
eth.Balance(celoutils.HexToAddress(accountStatusRequest.Address), nil).Returns(&networkBalance),
); err != nil {
return err
}
if networkNonce > 0 {
networkNonce--
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"balance": fmt.Sprintf("%s CELO", w3.FromWei(&networkBalance, 18)),
"nonce": networkNonce,
},
})
}
}

View File

@ -1,74 +0,0 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// HandleSignTransfer route.
// POST: /api/sign/transfer
// JSON Body:
// from -> ETH address
// to -> ETH address
// voucherAddress -> ETH address
// amount -> int (6 d.p. precision)
// e.g. 1000000 = 1 VOUCHER
// Returns the task id.
func HandleSignTransfer(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
req struct {
From string `json:"from" validate:"required,eth_checksum"`
To string `json:"to" validate:"required,eth_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_checksum"`
Amount uint64 `json:"amount" validate:"required"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(err)
}
if err := c.Validate(req); err != nil {
return err
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferPayload{
TrackingId: trackingId,
From: req.From,
To: req.To,
VoucherAddress: req.VoucherAddress,
Amount: req.Amount,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTask,
tasker.HighPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}

View File

@ -0,0 +1,98 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// HandleSignTransfer godoc
//
// @Summary Sign and dispatch transfer request.
// @Description Sign and dispatch a transfer request.
// @Tags network
// @Accept json
// @Produce json
// @Param signTransferRequest body object{from=string,to=string,voucherAddress=string,amount=uint64} true "Sign Transfer Request"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /sign/transfer [post]
func HandleSignTransfer(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
req struct {
From string `json:"from" validate:"required,eth_addr_checksum"`
To string `json:"to" validate:"required,eth_addr_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_addr_checksum"`
Amount uint64 `json:"amount" validate:"gt=0"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(req); err != nil {
return err
}
accountActive, gasLock, err := cu.Store.GetAccountStatus(c.Request().Context(), req.From)
if err != nil {
return err
}
if !accountActive {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Account pending activation. Try again later.",
})
}
if gasLock {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Gas lock. Gas balance unavailable. Try again later.",
})
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferPayload{
TrackingId: trackingId,
From: req.From,
To: req.To,
VoucherAddress: req.VoucherAddress,
Amount: req.Amount,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTask,
tasker.HighPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}
}

View File

@ -0,0 +1,107 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// Max 10k vouchers per approval session
const approvalSafetyLimit = 10000 * 1000000
// HandleSignTransferAuthorization godoc
//
// @Summary Sign and dispatch a transfer authorization (approve) request.
// @Description Sign and dispatch a transfer authorization (approve) request.
// @Tags network
// @Accept json
// @Produce json
// @Param signTransferAuthorzationRequest body object{amount=uint64,authorizer=string,authorizedAddress=string,voucherAddress=string} true "Sign Transfer Authorization (approve) Request"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /sign/transferAuth [post]
func HandleSignTranserAuthorization(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
req struct {
Amount uint64 `json:"amount" validate:"gte=0"`
Authorizer string `json:"authorizer" validate:"required,eth_addr_checksum"`
AuthorizedAddress string `json:"authorizedAddress" validate:"required,eth_addr_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_addr_checksum"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(req); err != nil {
return err
}
accountActive, gasLock, err := cu.Store.GetAccountStatus(c.Request().Context(), req.Authorizer)
if err != nil {
return err
}
if req.Amount > approvalSafetyLimit {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Approval amount per session exceeds 10k.",
})
}
if !accountActive {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Account pending activation. Try again later.",
})
}
if gasLock {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Gas lock. Gas balance unavailable. Try again later.",
})
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferAuthPayload{
TrackingId: trackingId,
Amount: req.Amount,
Authorizer: req.Authorizer,
AuthorizedAddress: req.AuthorizedAddress,
VoucherAddress: req.VoucherAddress,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTaskAuth,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}
}

15
internal/api/swagger.go Normal file
View File

@ -0,0 +1,15 @@
package api
// @title CIC Custodial API
// @version 1.0
// @description Interact with CIC Custodial API
// @termsOfService https://grassecon.org/pages/terms-and-conditions.html
// @contact.name API Support
// @contact.url https://grassecon.org/pages/contact-us
// @contact.email devops@grassecon.org
// @license.name AGPL-3.0
// @license.url https://www.gnu.org/licenses/agpl-3.0.en.html
// @BasePath /api

View File

@ -7,36 +7,43 @@ import (
"github.com/labstack/echo/v4"
)
// HandleTxStatus route.
// GET: /api/track/:trackingId
// Route param:
// trackingId -> tracking UUID
// Returns array of tx status.
func HandleTrackTx(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
txStatusRequest struct {
TrackingId string `param:"trackingId" validate:"required,uuid"`
// HandleTrackTx godoc
// @Summary Track an OTX (Origin transaction) status.
// @Description Track an OTX (Origin transaction) status.
// @Tags track
// @Accept */*
// @Produce json
// @Param trackingId path string true "Tracking Id"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /track/{trackingId} [get]
func HandleTrackTx(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
txStatusRequest struct {
TrackingId string `param:"trackingId" validate:"required,uuid"`
}
)
if err := c.Bind(&txStatusRequest); err != nil {
return NewBadRequestError(err)
}
)
if err := c.Bind(&txStatusRequest); err != nil {
return NewBadRequestError(err)
if err := c.Validate(txStatusRequest); err != nil {
return err
}
txs, err := cu.Store.GetTxStatus(c.Request().Context(), txStatusRequest.TrackingId)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"transaction": txs,
},
})
}
if err := c.Validate(txStatusRequest); err != nil {
return err
}
txs, err := cu.PgStore.GetTxStatusByTrackingId(c.Request().Context(), txStatusRequest.TrackingId)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"transactions": txs,
},
})
}

View File

@ -1,5 +1,11 @@
package api
import "errors"
var (
ErrInvalidJSON = errors.New("Invalid JSON structure.")
)
type H map[string]any
type OkResp struct {

View File

@ -1,7 +1,6 @@
package api
import (
"github.com/celo-org/celo-blockchain/common"
"github.com/go-playground/validator/v10"
)
@ -17,12 +16,3 @@ func (v *Validator) Validate(i interface{}) error {
}
return nil
}
func EthChecksumValidator(fl validator.FieldLevel) bool {
addr, err := common.NewMixedcaseAddressFromString(fl.Field().String())
if err != nil {
return false
}
return addr.ValidChecksum()
}

View File

@ -0,0 +1,28 @@
package custodial
import "github.com/grassrootseconomics/w3-celo-patch"
const (
Approve = "approve"
Check = "check"
GiveTo = "giveTo"
MintTo = "mintTo"
NextTime = "nextTime"
Register = "register"
Transfer = "transfer"
TransferFrom = "transferFrom"
)
// Define common smart contrcat ABI's that can be injected into the system container.
// Any relevant function signature that will be used by the custodial system can be defined here.
func initAbis() map[string]*w3.Func {
return map[string]*w3.Func{
Approve: w3.MustNewFunc("approve(address, uint256)", "bool"),
Check: w3.MustNewFunc("check(address)", "bool"),
GiveTo: w3.MustNewFunc("giveTo(address)", "uint256"),
MintTo: w3.MustNewFunc("mintTo(address, uint256)", "bool"),
NextTime: w3.MustNewFunc("nextTime(address)", "uint256"),
Register: w3.MustNewFunc("register(address)", ""),
Transfer: w3.MustNewFunc("transfer(address,uint256)", "bool"),
}
}

View File

@ -1,22 +1,89 @@
package custodial
import (
"context"
"crypto/ecdsa"
"time"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/redis/go-redis/v9"
"github.com/zerodha/logf"
)
type Custodial struct {
CeloProvider *celoutils.Provider
EventEmitter events.EventEmitter
Keystore keystore.Keystore
LockProvider *redislock.Client
Noncestore nonce.Noncestore
PgStore store.Store
SystemContainer *tasker.SystemContainer
TaskerClient *tasker.TaskerClient
type (
Opts struct {
ApprovalTimeout time.Duration
CeloProvider *celoutils.Provider
LockProvider *redislock.Client
Logg logf.Logger
Noncestore nonce.Noncestore
Store store.Store
RedisClient *redis.Client
RegistryAddress string
SystemPrivateKey string
SystemPublicKey string
TaskerClient *tasker.TaskerClient
}
Custodial struct {
ApprovalTimeout time.Duration
Abis map[string]*w3.Func
CeloProvider *celoutils.Provider
LockProvider *redislock.Client
Logg logf.Logger
Noncestore nonce.Noncestore
Store store.Store
RedisClient *redis.Client
RegistryMap map[string]common.Address
SystemPrivateKey *ecdsa.PrivateKey
SystemPublicKey string
TaskerClient *tasker.TaskerClient
}
)
func NewCustodial(o Opts) (*Custodial, error) {
ctx, cancel := context.WithTimeout(context.Background(), util.SLATimeout)
defer cancel()
registryMap, err := o.CeloProvider.RegistryMap(ctx, celoutils.HexToAddress(o.RegistryAddress))
if err != nil {
o.Logg.Error("custodial: critical error loading contracts from registry: %v", err)
return nil, err
}
systemNonce, err := o.Noncestore.Peek(ctx, o.SystemPublicKey)
if err != nil {
return nil, err
}
o.Logg.Info("custodial: loaded_nonce", "system_nonce", systemNonce)
privateKey, err := eth_crypto.HexToECDSA(o.SystemPrivateKey)
if err != nil {
return nil, err
}
return &Custodial{
ApprovalTimeout: o.ApprovalTimeout,
Abis: initAbis(),
CeloProvider: o.CeloProvider,
LockProvider: o.LockProvider,
Logg: o.Logg,
Noncestore: o.Noncestore,
Store: o.Store,
RedisClient: o.RedisClient,
RegistryMap: registryMap,
SystemPrivateKey: privateKey,
SystemPublicKey: o.SystemPublicKey,
TaskerClient: o.TaskerClient,
}, nil
}

View File

@ -1,12 +0,0 @@
package events
type EventEmitter interface {
Close()
Publish(subject string, dedupId string, eventPayload interface{}) error
}
type EventPayload struct {
OtxId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}

View File

@ -1,87 +0,0 @@
package events
import (
"encoding/json"
"time"
"github.com/nats-io/nats.go"
)
const (
StreamName string = "CUSTODIAL"
StreamSubjects string = "CUSTODIAL.*"
// Subjects
AccountNewNonce string = "CUSTODIAL.accountNewNonce"
AccountRegister string = "CUSTODIAL.accountRegister"
AccountGiftGas string = "CUSTODIAL.systemNewAccountGas"
AccountGiftVoucher string = "CUSTODIAL.systemNewAccountVoucher"
AccountRefillGas string = "CUSTODIAL.systemRefillAccountGas"
DispatchFail string = "CUSTODIAL.dispatchFail"
DispatchSuccess string = "CUSTODIAL.dispatchSuccess"
SignTransfer string = "CUSTODIAL.signTransfer"
)
type JetStreamOpts struct {
ServerUrl string
PersistDuration time.Duration
DedupDuration time.Duration
}
type JetStream struct {
jsCtx nats.JetStreamContext
nc *nats.Conn
}
func NewJetStreamEventEmitter(o JetStreamOpts) (EventEmitter, error) {
natsConn, err := nats.Connect(o.ServerUrl)
if err != nil {
return nil, err
}
js, err := natsConn.JetStream()
if err != nil {
return nil, err
}
// Bootstrap stream if it doesn't exist.
stream, _ := js.StreamInfo(StreamName)
if stream == nil {
_, err = js.AddStream(&nats.StreamConfig{
Name: StreamName,
MaxAge: o.PersistDuration,
Storage: nats.FileStorage,
Subjects: []string{StreamSubjects},
Duplicates: o.DedupDuration,
})
if err != nil {
return nil, err
}
}
return &JetStream{
jsCtx: js,
nc: natsConn,
}, nil
}
// Close gracefully shutdowns the JetStream connection.
func (js *JetStream) Close() {
if js.nc != nil {
js.nc.Close()
}
}
// Publish publishes the JSON data to the NATS stream.
func (js *JetStream) Publish(subject string, dedupId string, eventPayload interface{}) error {
jsonData, err := json.Marshal(eventPayload)
if err != nil {
return err
}
_, err = js.jsCtx.Publish(subject, jsonData, nats.MsgId(dedupId))
if err != nil {
return err
}
return nil
}

View File

@ -1,14 +0,0 @@
package keystore
import (
"context"
"crypto/ecdsa"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
)
// Keystore defines how keypairs should be stored and accessed from a storage backend.
type Keystore interface {
WriteKeyPair(context.Context, keypair.Key) (uint, error)
LoadPrivateKey(context.Context, string) (*ecdsa.PrivateKey, error)
}

View File

@ -1,61 +0,0 @@
package keystore
import (
"context"
"crypto/ecdsa"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
"github.com/jackc/pgx/v5/pgxpool"
)
type (
Opts struct {
PostgresPool *pgxpool.Pool
Queries *queries.Queries
}
PostgresKeystore struct {
db *pgxpool.Pool
queries *queries.Queries
}
)
func NewPostgresKeytore(o Opts) Keystore {
return &PostgresKeystore{
db: o.PostgresPool,
queries: o.Queries,
}
}
// WriteKeyPair inserts a keypair into the db and returns the linked id.
func (ks *PostgresKeystore) WriteKeyPair(ctx context.Context, keypair keypair.Key) (uint, error) {
var (
id uint
)
if err := ks.db.QueryRow(ctx, ks.queries.WriteKeyPair, keypair.Public, keypair.Private).Scan(&id); err != nil {
return id, err
}
return id, nil
}
// LoadPrivateKey loads a private key as a crypto primitive for direct use. An id is used to search for the private key.
func (ks *PostgresKeystore) LoadPrivateKey(ctx context.Context, publicKey string) (*ecdsa.PrivateKey, error) {
var (
privateKeyString string
)
if err := ks.db.QueryRow(ctx, ks.queries.LoadKeyPair, publicKey).Scan(&privateKeyString); err != nil {
return nil, err
}
privateKey, err := eth_crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}
return privateKey, nil
}

View File

@ -7,6 +7,5 @@ type Noncestore interface {
Peek(context.Context, string) (uint64, error)
Acquire(context.Context, string) (uint64, error)
Return(context.Context, string) error
SyncNetworkNonce(context.Context, string) (uint64, error)
SetNewAccountNonce(context.Context, string) error
SetAccountNonce(context.Context, string, uint64) error
}

View File

@ -2,35 +2,52 @@ package nonce
import (
"context"
"errors"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/store"
redispool "github.com/grassrootseconomics/cic-custodial/pkg/redis"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/jackc/pgx/v5"
"github.com/redis/go-redis/v9"
)
type Opts struct {
RedisPool *redispool.RedisPool
CeloProvider *celoutils.Provider
}
type (
Opts struct {
ChainProvider *celoutils.Provider
RedisPool *redispool.RedisPool
Store store.Store
}
// RedisNoncestore implements `Noncestore`
type RedisNoncestore struct {
chainProvider *celoutils.Provider
redis *redispool.RedisPool
}
// RedisNoncestore implements `Noncestore`
RedisNoncestore struct {
chainProvider *celoutils.Provider
redis *redispool.RedisPool
store store.Store
}
)
func NewRedisNoncestore(o Opts) Noncestore {
return &RedisNoncestore{
chainProvider: o.ChainProvider,
redis: o.RedisPool,
chainProvider: o.CeloProvider,
store: o.Store,
}
}
func (n *RedisNoncestore) Peek(ctx context.Context, publicKey string) (uint64, error) {
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
if err != nil {
return 0, err
if err == redis.Nil {
nonce, err = n.bootstrap(ctx, publicKey)
if err != nil {
return 0, err
}
return nonce, nil
} else {
return 0, err
}
}
return nonce, nil
@ -43,7 +60,14 @@ func (n *RedisNoncestore) Acquire(ctx context.Context, publicKey string) (uint64
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
if err != nil {
return 0, nil
if err == redis.Nil {
nonce, err = n.bootstrap(ctx, publicKey)
if err != nil {
return 0, err
}
} else {
return 0, err
}
}
err = n.redis.Client.Incr(ctx, publicKey).Err()
@ -70,32 +94,37 @@ func (n *RedisNoncestore) Return(ctx context.Context, publicKey string) error {
return nil
}
func (n *RedisNoncestore) SyncNetworkNonce(ctx context.Context, publicKey string) (uint64, error) {
var (
networkNonce uint64
)
err := n.chainProvider.Client.CallCtx(
ctx,
eth.Nonce(w3.A(publicKey), nil).Returns(&networkNonce),
)
if err != nil {
return 0, err
}
err = n.redis.Client.Set(ctx, publicKey, networkNonce, 0).Err()
if err != nil {
return 0, err
}
return networkNonce, nil
}
func (n *RedisNoncestore) SetNewAccountNonce(ctx context.Context, publicKey string) error {
err := n.redis.Client.Set(ctx, publicKey, 0, 0).Err()
if err != nil {
func (n *RedisNoncestore) SetAccountNonce(ctx context.Context, publicKey string, nonce uint64) error {
if err := n.redis.Client.Set(ctx, publicKey, nonce, 0).Err(); err != nil {
return err
}
return nil
}
// bootstrap can be used to restore a destroyed redis nonce cache automatically.
// It first uses the otx_sign table as a source of nonce values.
// If the otx_sign table is corrupted, it can fallback to the network nonce.
// Ideally, the redis nonce cache should never be lost.
func (n *RedisNoncestore) bootstrap(ctx context.Context, publicKey string) (uint64, error) {
lastDbNonce, err := n.store.GetNextNonce(ctx, publicKey)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
err := n.chainProvider.Client.CallCtx(
ctx,
eth.Nonce(celoutils.HexToAddress(publicKey), nil).Returns(&lastDbNonce),
)
if err != nil {
return 0, err
}
} else {
return 0, err
}
}
if err := n.SetAccountNonce(ctx, publicKey, lastDbNonce); err != nil {
return 0, err
}
return lastDbNonce, nil
}

View File

@ -1,28 +0,0 @@
package queries
import (
"fmt"
"github.com/knadh/goyesql/v2"
)
type Queries struct {
// Keystore
WriteKeyPair string `query:"write-key-pair"`
LoadKeyPair string `query:"load-key-pair"`
// Store
CreateOTX string `query:"create-otx"`
CreateDispatchStatus string `query:"create-dispatch-status"`
UpdateChainStatus string `query:"update-chain-status"`
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
}
func LoadQueries(q goyesql.Queries) (*Queries, error) {
loadedQueries := &Queries{}
if err := goyesql.ScanToStruct(loadedQueries, q, nil); err != nil {
return nil, fmt.Errorf("failed to scan queries %v", err)
}
return loadedQueries, nil
}

73
internal/store/account.go Normal file
View File

@ -0,0 +1,73 @@
package store
import (
"context"
)
func (s *PgStore) ActivateAccount(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.ActivateAccount,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PgStore) GetAccountStatus(
ctx context.Context,
publicAddress string,
) (bool, bool, error) {
var (
accountActive bool
gasLock bool
)
if err := s.db.QueryRow(
ctx,
s.queries.GetAccountStatus,
publicAddress,
).Scan(
&accountActive,
&gasLock,
); err != nil {
return false, false, err
}
return accountActive, gasLock, nil
}
func (s *PgStore) GasLock(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.GasLock,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PgStore) GasUnlock(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.GasUnlock,
publicAddress,
); err != nil {
return err
}
return nil
}

View File

@ -1,34 +0,0 @@
package store
import (
"context"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
)
func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error {
if _, err := s.db.Exec(
ctx,
s.queries.CreateDispatchStatus,
dispatch.OtxId,
dispatch.Status,
); err != nil {
return err
}
return nil
}
func (s *PostgresStore) UpdateChainStatus(ctx context.Context, txHash string, status enum.OtxStatus, block uint64) error {
if _, err := s.db.Exec(
ctx,
s.queries.UpdateChainStatus,
txHash,
status,
block,
); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,53 @@
package store
import (
"context"
"crypto/ecdsa"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
)
func (s *PgStore) WriteKeyPair(
ctx context.Context,
keypair keypair.Key,
) (uint, error) {
var (
id uint
)
if err := s.db.QueryRow(
ctx,
s.queries.WriteKeyPair,
keypair.Public,
keypair.Private,
).Scan(&id); err != nil {
return id, err
}
return id, nil
}
func (s *PgStore) LoadPrivateKey(
ctx context.Context,
publicKey string,
) (*ecdsa.PrivateKey, error) {
var (
privateKeyString string
)
if err := s.db.QueryRow(
ctx,
s.queries.LoadKeyPair,
publicKey,
).Scan(&privateKeyString); err != nil {
return nil, err
}
privateKey, err := eth_crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}
return privateKey, nil
}

View File

@ -2,20 +2,39 @@ package store
import (
"context"
"math/big"
"time"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
)
type TxStatus struct {
Type string `db:"type" json:"txType"`
TxHash string `db:"tx_hash" json:"txHash"`
TransferValue uint64 `db:"transfer_value" json:"transferValue"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
Status string `db:"status" json:"status"`
}
type (
Otx struct {
TrackingId string
Type enum.OtxType
RawTx string
TxHash string
From string
Data string
GasLimit uint64
TransferValue uint64
GasPrice *big.Int
Nonce uint64
}
TxStatus struct {
CreatedAt time.Time `db:"created_at" json:"createdAt"`
Status string `db:"status" json:"status"`
TransferValue uint64 `db:"transfer_value" json:"transferValue"`
TxHash string `db:"tx_hash" json:"txHash"`
Type string `db:"type" json:"txType"`
}
)
func (s *PostgresStore) CreateOtx(ctx context.Context, otx OTX) (uint, error) {
func (s *PgStore) CreateOtx(
ctx context.Context,
otx Otx,
) (uint, error) {
var (
id uint
)
@ -33,27 +52,103 @@ func (s *PostgresStore) CreateOtx(ctx context.Context, otx OTX) (uint, error) {
otx.GasLimit,
otx.TransferValue,
otx.Nonce,
).Scan(&id); err != nil {
).Scan(
&id,
); err != nil {
return id, err
}
return id, nil
}
func (s *PostgresStore) GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error) {
func (s *PgStore) GetNextNonce(
ctx context.Context,
publicAddress string,
) (uint64, error) {
var (
txs []*TxStatus
lastNonce uint64
)
if err := pgxscan.Select(
if err := s.db.QueryRow(
ctx,
s.db,
&txs,
s.queries.GetTxStatusByTrackingId,
trackingId,
s.queries.GetNextNonce,
publicAddress,
).Scan(
&lastNonce,
); err != nil {
return nil, err
return 0, err
}
return txs, nil
return lastNonce, nil
}
func (s *PgStore) GetTxStatus(
ctx context.Context,
trackingId string,
) (TxStatus, error) {
var (
tx TxStatus
)
rows, err := s.db.Query(
ctx,
s.queries.GetTxStatusByTrackingId,
trackingId,
)
if err != nil {
return tx, err
}
if err := pgxscan.ScanOne(
&tx,
rows,
); err != nil {
return tx, err
}
return tx, nil
}
func (s *PgStore) CreateDispatchStatus(
ctx context.Context,
otxId uint,
otxStatus enum.OtxStatus,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.CreateDispatchStatus,
otxId,
otxStatus,
); err != nil {
return err
}
return nil
}
func (s *PgStore) UpdateDispatchStatus(
ctx context.Context,
txSuccess bool,
txHash string,
txBlock uint64,
) error {
var (
status = enum.SUCCESS
)
if !txSuccess {
status = enum.REVERTED
}
if _, err := s.db.Exec(
ctx,
s.queries.UpdateDispatchStatus,
txHash,
status,
txBlock,
); err != nil {
return err
}
return nil
}

View File

@ -1,25 +0,0 @@
package store
import (
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/jackc/pgx/v5/pgxpool"
)
type (
Opts struct {
PostgresPool *pgxpool.Pool
Queries *queries.Queries
}
PostgresStore struct {
db *pgxpool.Pool
queries *queries.Queries
}
)
func NewPostgresStore(o Opts) Store {
return &PostgresStore{
db: o.PostgresPool,
queries: o.Queries,
}
}

View File

@ -2,33 +2,129 @@ package store
import (
"context"
"crypto/ecdsa"
"fmt"
"os"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/tern/v2/migrate"
"github.com/knadh/goyesql/v2"
)
type (
OTX struct {
TrackingId string
Type enum.OtxType
RawTx string
TxHash string
From string
Data string
GasLimit uint64
TransferValue uint64
GasPrice uint64
Nonce uint64
}
DispatchStatus struct {
OtxId uint
Status enum.OtxStatus
}
Store interface {
CreateOtx(ctx context.Context, otx OTX) (id uint, err error)
CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error
GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error)
UpdateChainStatus(ctx context.Context, txHash string, status enum.OtxStatus, block uint64) error
// Keypair related actions.
LoadPrivateKey(context.Context, string) (*ecdsa.PrivateKey, error)
WriteKeyPair(context.Context, keypair.Key) (uint, error)
// Otx related actions.
CreateOtx(context.Context, Otx) (uint, error)
GetNextNonce(context.Context, string) (uint64, error)
GetTxStatus(context.Context, string) (TxStatus, error)
CreateDispatchStatus(context.Context, uint, enum.OtxStatus) error
UpdateDispatchStatus(context.Context, bool, string, uint64) error
// Account related actions.
ActivateAccount(context.Context, string) error
GetAccountStatus(context.Context, string) (bool, bool, error)
// Gas quota related actions.
GasLock(context.Context, string) error
GasUnlock(context.Context, string) error
}
Opts struct {
DSN string
MigrationsFolderPath string
QueriesFolderPath string
}
PgStore struct {
db *pgxpool.Pool
queries *queries
}
queries struct {
// Keystore related queries.
WriteKeyPair string `query:"write-key-pair"`
LoadKeyPair string `query:"load-key-pair"`
// Otx related queries.
CreateOTX string `query:"create-otx"`
GetNextNonce string `query:"get-next-nonce"`
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
CreateDispatchStatus string `query:"create-dispatch-status"`
UpdateDispatchStatus string `query:"update-dispatch-status"`
// Account related queries.
ActivateAccount string `query:"activate-account"`
GetAccountStatus string `query:"get-account-status-by-address"`
GasLock string `query:"acc-gas-lock"`
GasUnlock string `query:"acc-gas-unlock"`
}
)
func NewPgStore(o Opts) (Store, error) {
parsedConfig, err := pgxpool.ParseConfig(o.DSN)
if err != nil {
return nil, err
}
dbPool, err := pgxpool.NewWithConfig(context.Background(), parsedConfig)
if err != nil {
return nil, err
}
queries, err := loadQueries(o.QueriesFolderPath)
if err != nil {
return nil, err
}
if err := runMigrations(context.Background(), dbPool, o.MigrationsFolderPath); err != nil {
return nil, err
}
return &PgStore{
db: dbPool,
queries: queries,
}, nil
}
func loadQueries(queriesPath string) (*queries, error) {
parsedQueries, err := goyesql.ParseFile(queriesPath)
if err != nil {
return nil, err
}
loadedQueries := &queries{}
if err := goyesql.ScanToStruct(loadedQueries, parsedQueries, nil); err != nil {
return nil, fmt.Errorf("failed to scan queries %v", err)
}
return loadedQueries, nil
}
func runMigrations(ctx context.Context, dbPool *pgxpool.Pool, migrationsPath string) error {
ctx, cancel := context.WithTimeout(ctx, util.SLATimeout)
defer cancel()
conn, err := dbPool.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
migrator, err := migrate.NewMigrator(ctx, conn.Conn(), "schema_version")
if err != nil {
return err
}
if err := migrator.LoadMigrations(os.DirFS(migrationsPath)); err != nil {
return err
}
if err := migrator.Migrate(ctx); err != nil {
return err
}
return nil
}

59
internal/sub/handler.go Normal file
View File

@ -0,0 +1,59 @@
package sub
import (
"context"
"encoding/json"
"github.com/nats-io/nats.go"
)
type (
ChainEvent struct {
Block uint64 `json:"block"`
From string `json:"from"`
To string `json:"to"`
ContractAddress string `json:"contractAddress"`
Success bool `json:"success"`
TxHash string `json:"transactionHash"`
TxIndex uint `json:"transactionIndex"`
Value uint64 `json:"value"`
}
)
func (s *Sub) processEventHandler(ctx context.Context, msg *nats.Msg) error {
var (
chainEvent ChainEvent
)
if err := json.Unmarshal(msg.Data, &chainEvent); err != nil {
return err
}
if err := s.cu.Store.UpdateDispatchStatus(
ctx,
chainEvent.Success,
chainEvent.TxHash,
chainEvent.Block,
); err != nil {
return err
}
if chainEvent.Success {
switch msg.Subject {
case "CHAIN.register":
if err := s.cu.Store.ActivateAccount(ctx, chainEvent.To); err != nil {
return err
}
if err := s.cu.Store.GasUnlock(ctx, chainEvent.To); err != nil {
return err
}
case "CHAIN.gas":
if err := s.cu.Store.GasUnlock(ctx, chainEvent.To); err != nil {
return err
}
}
}
return nil
}

95
internal/sub/jetstream.go Normal file
View File

@ -0,0 +1,95 @@
package sub
import (
"context"
"errors"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/nats-io/nats.go"
"github.com/zerodha/logf"
)
const (
durableId = "cic-custodial"
pullStream = "CHAIN"
pullSubject = "CHAIN.*"
)
type (
SubOpts struct {
CustodialContainer *custodial.Custodial
JsCtx nats.JetStreamContext
Logg logf.Logger
NatsConn *nats.Conn
}
Sub struct {
cu *custodial.Custodial
jsCtx nats.JetStreamContext
logg logf.Logger
natsConn *nats.Conn
}
)
func NewSub(o SubOpts) (*Sub, error) {
_, err := o.JsCtx.AddConsumer(pullStream, &nats.ConsumerConfig{
Durable: durableId,
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: pullSubject,
})
if err != nil {
return nil, err
}
return &Sub{
cu: o.CustodialContainer,
jsCtx: o.JsCtx,
logg: o.Logg,
natsConn: o.NatsConn,
}, nil
}
func (s *Sub) Process() error {
subOpts := []nats.SubOpt{
nats.ManualAck(),
nats.Bind(pullStream, durableId),
}
natsSub, err := s.jsCtx.PullSubscribe(pullSubject, durableId, subOpts...)
if err != nil {
return err
}
for {
events, err := natsSub.Fetch(1)
if err != nil {
if errors.Is(err, nats.ErrTimeout) {
continue
} else if errors.Is(err, nats.ErrConnectionClosed) {
return nil
} else {
return err
}
}
if len(events) > 0 {
msg := events[0]
ctx, cancel := context.WithTimeout(context.Background(), util.SLATimeout)
if err := s.processEventHandler(ctx, msg); err != nil {
s.logg.Error("sub: handler error", "error", err)
msg.Nak()
} else {
msg.Ack()
}
cancel()
}
}
}
func (s *Sub) Close() {
if s.natsConn != nil {
s.natsConn.Close()
}
}

View File

@ -10,17 +10,16 @@ import (
)
const (
taskTimeout = 60
taskTimeout = 60 * time.Second
taskRetention = 48 * time.Hour
)
type TaskerClientOpts struct {
RedisPool *redis.RedisPool
TaskRetention time.Duration
RedisPool *redis.RedisPool
}
type TaskerClient struct {
Client *asynq.Client
taskRetention time.Duration
Client *asynq.Client
}
func NewTaskerClient(o TaskerClientOpts) *TaskerClient {
@ -29,18 +28,23 @@ func NewTaskerClient(o TaskerClientOpts) *TaskerClient {
}
}
func (c *TaskerClient) CreateTask(ctx context.Context, taskName TaskName, queueName QueueName, task *Task) (*asynq.TaskInfo, error) {
func (c *TaskerClient) CreateTask(ctx context.Context, taskName TaskName, queueName QueueName, task *Task, extraOpts ...asynq.Option) (*asynq.TaskInfo, error) {
if task.Id == "" {
task.Id = uuid.NewString()
}
defaultOpts := []asynq.Option{
asynq.Queue(string(queueName)),
asynq.TaskID(task.Id),
asynq.Retention(taskRetention),
asynq.Timeout(taskTimeout),
}
defaultOpts = append(defaultOpts, extraOpts...)
qTask := asynq.NewTask(
string(taskName),
task.Payload,
asynq.Queue(string(queueName)),
asynq.TaskID(task.Id),
asynq.Retention(c.taskRetention),
asynq.Timeout(taskTimeout*time.Second),
defaultOpts...,
)
taskInfo, err := c.Client.EnqueueContext(ctx, qTask)

View File

@ -4,22 +4,24 @@ import (
"context"
"time"
"github.com/bsm/redislock"
"github.com/grassrootseconomics/cic-custodial/pkg/logg"
"github.com/grassrootseconomics/cic-custodial/pkg/redis"
"github.com/hibiken/asynq"
"github.com/zerodha/logf"
)
const (
fixedRetryCount = 25
fixedRetryPeriod = time.Second * 1
retryRequeueInterval = 1 * time.Second
)
type TaskerServerOpts struct {
Concurrency int
Logg logf.Logger
LogLevel asynq.LogLevel
RedisPool *redis.RedisPool
Concurrency int
ErrorHandler asynq.ErrorHandler
IsFailureHandler func(error) bool
Logg logf.Logger
LogLevel asynq.LogLevel
RedisPool *redis.RedisPool
RetryHandler asynq.RetryDelayFunc
}
type TaskerServer struct {
@ -31,14 +33,17 @@ func NewTaskerServer(o TaskerServerOpts) *TaskerServer {
server := asynq.NewServer(
o.RedisPool,
asynq.Config{
Concurrency: o.Concurrency,
IsFailure: expectedFailures,
LogLevel: o.LogLevel,
Concurrency: o.Concurrency,
DelayedTaskCheckInterval: retryRequeueInterval,
ErrorHandler: o.ErrorHandler,
IsFailure: o.IsFailureHandler,
LogLevel: o.LogLevel,
Logger: logg.AsynqCompatibleLogger(o.Logg),
Queues: map[string]int{
string(HighPriority): 5,
string(DefaultPriority): 2,
string(HighPriority): 7,
string(DefaultPriority): 3,
},
RetryDelayFunc: retryDelay,
RetryDelayFunc: o.RetryHandler,
},
)
@ -50,6 +55,10 @@ func NewTaskerServer(o TaskerServerOpts) *TaskerServer {
}
}
func (ts *TaskerServer) RegisterMiddlewareStack(middlewareStack []asynq.MiddlewareFunc) {
ts.mux.Use(middlewareStack...)
}
func (ts *TaskerServer) RegisterHandlers(taskName TaskName, taskHandler func(context.Context, *asynq.Task) error) {
ts.mux.HandleFunc(string(taskName), taskHandler)
}
@ -66,22 +75,3 @@ func (ts *TaskerServer) Stop() {
ts.server.Stop()
ts.server.Shutdown()
}
func expectedFailures(err error) bool {
switch err {
// Ignore lock contention errors; retry until lock obtain.
case redislock.ErrNotObtained:
return false
default:
return true
}
}
// Immidiatel
func retryDelay(count int, err error, task *asynq.Task) time.Duration {
if count < fixedRetryCount {
return fixedRetryPeriod
} else {
return asynq.DefaultRetryDelayFunc(count, err, task)
}
}

View File

@ -1,123 +0,0 @@
package task
import (
"context"
"encoding/json"
"fmt"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
func AccountGiftGasProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
}
lock, err := cu.LockProvider.Obtain(
ctx,
cu.SystemContainer.LockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
nil,
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
err = nErr
}
}
}()
builtTx, err := cu.CeloProvider.SignGasTransferTx(
cu.SystemContainer.PrivateKey,
celoutils.GasTransferTxOpts{
To: w3.A(payload.PublicKey),
Nonce: nonce,
Value: cu.SystemContainer.GiftableGasValue,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.GIFT_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableGasValue.Uint64(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
eventPayload := &events.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.EventEmitter.Publish(
events.AccountGiftGas,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -1,133 +0,0 @@
package task
import (
"context"
"encoding/json"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
func GiftVoucherProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
cu.SystemContainer.LockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
nil,
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["mintTo"].EncodeArgs(
w3.A(payload.PublicKey),
cu.SystemContainer.GiftableTokenValue,
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemContainer.PrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.SystemContainer.GiftableToken,
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
Nonce: nonce,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.ACCOUNT_REGISTER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableTokenValue.Uint64(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
eventPayload := &events.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.EventEmitter.Publish(
events.AccountGiftVoucher,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -1,81 +0,0 @@
package task
import (
"context"
"encoding/json"
"fmt"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/hibiken/asynq"
)
type AccountPayload struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}
func AccountPrepare(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var payload AccountPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.Noncestore.SetNewAccountNonce(ctx, payload.PublicKey); err != nil {
return err
}
_, err := cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRegisterTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountGiftGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountGiftVoucherTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
eventPayload := events.EventPayload{
TrackingId: payload.TrackingId,
}
if err := cu.EventEmitter.Publish(
events.AccountNewNonce,
payload.PublicKey,
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -3,17 +3,16 @@ package task
import (
"context"
"encoding/json"
"fmt"
"math/big"
"time"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
@ -21,57 +20,104 @@ import (
func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
payload AccountPayload
balance big.Int
nextTime big.Int
checkStatus bool
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
return err
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.Balance(w3.A(payload.PublicKey), nil).Returns(&balance),
eth.CallFunc(
cu.Abis[custodial.NextTime],
cu.RegistryMap[celoutils.GasFaucet],
celoutils.HexToAddress(payload.PublicKey),
).Returns(&nextTime),
); err != nil {
return err
}
if belowThreshold := balance.Cmp(cu.SystemContainer.GasRefillThreshold); belowThreshold > 0 {
// The user recently requested funds, there is a cooldown applied.
// We can schedule an attempt after the cooldown period has passed + 10 seconds.
if nextTime.Int64() > time.Now().Unix() {
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
asynq.ProcessAt(time.Unix(nextTime.Int64()+10, 0)),
)
if err != nil {
return err
}
return nil
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.CallFunc(
cu.Abis[custodial.Check],
cu.RegistryMap[celoutils.GasFaucet],
celoutils.HexToAddress(payload.PublicKey),
).Returns(&checkStatus),
); err != nil {
return err
}
// The gas faucet backend returns a false status, a poke will fail.
if !checkStatus {
return nil
}
lock, err := cu.LockProvider.Obtain(
ctx,
cu.SystemContainer.LockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
nil,
lockPrefix+cu.SystemPublicKey,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemPublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemPublicKey); nErr != nil {
err = nErr
}
}
}()
// TODO: Review gas params
builtTx, err := cu.CeloProvider.SignGasTransferTx(
cu.SystemContainer.PrivateKey,
celoutils.GasTransferTxOpts{
To: w3.A(payload.PublicKey),
Nonce: nonce,
Value: cu.SystemContainer.GiftableGasValue,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
input, err := cu.Abis[custodial.GiveTo].EncodeArgs(
celoutils.HexToAddress(payload.PublicKey),
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemPrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.RegistryMap[celoutils.GasFaucet],
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
if err != nil {
@ -83,17 +129,16 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.REFILL_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableGasValue.Uint64(),
Nonce: builtTx.Nonce(),
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.REFILL_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
@ -119,20 +164,6 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
eventPayload := &events.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.EventEmitter.Publish(
events.AccountRefillGas,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -3,68 +3,73 @@ package task
import (
"context"
"encoding/json"
"fmt"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
type AccountPayload struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}
func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
cu.SystemContainer.LockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
nil,
lockPrefix+cu.SystemPublicKey,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemPublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemPublicKey); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["add"].EncodeArgs(
w3.A(payload.PublicKey),
input, err := cu.Abis[custodial.Register].EncodeArgs(
celoutils.HexToAddress(payload.PublicKey),
)
if err != nil {
return err
}
// TODO: Review gas params.
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemContainer.PrivateKey,
cu.SystemPrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.SystemContainer.AccountIndexContract,
ContractAddress: cu.RegistryMap[celoutils.CustodialProxy],
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
@ -77,14 +82,14 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.ACCOUNT_REGISTER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
Nonce: builtTx.Nonce(),
})
@ -112,17 +117,7 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
return err
}
eventPayload := &events.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.EventEmitter.Publish(
events.AccountRegister,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
if err := cu.Noncestore.SetAccountNonce(ctx, payload.PublicKey, 0); err != nil {
return err
}

View File

@ -9,8 +9,6 @@ import (
"github.com/celo-org/celo-blockchain/core/types"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
@ -25,53 +23,37 @@ func DispatchTx(cu *custodial.Custodial) func(context.Context, *asynq.Task) erro
return func(ctx context.Context, t *asynq.Task) error {
var (
payload TxPayload
dispatchStatus store.DispatchStatus
eventPayload events.EventPayload
dispathchTx common.Hash
dispatchStatus enum.OtxStatus = enum.IN_NETWORK
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
return err
}
txHash := payload.Tx.Hash().Hex()
dispatchStatus.OtxId, eventPayload.OtxId = payload.OtxId, payload.OtxId
eventPayload.TxHash = txHash
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.SendTx(payload.Tx).Returns(&dispathchTx),
); err != nil {
switch err.Error() {
case celoutils.ErrGasPriceLow:
dispatchStatus.Status = enum.FAIL_LOW_GAS_PRICE
dispatchStatus = enum.FAIL_LOW_GAS_PRICE
case celoutils.ErrInsufficientGas:
dispatchStatus.Status = enum.FAIL_NO_GAS
dispatchStatus = enum.FAIL_NO_GAS
case celoutils.ErrNonceLow:
dispatchStatus.Status = enum.FAIL_LOW_NONCE
dispatchStatus = enum.FAIL_LOW_NONCE
default:
dispatchStatus.Status = enum.FAIL_UNKNOWN_RPC_ERROR
dispatchStatus = enum.FAIL_UNKNOWN_RPC_ERROR
}
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.EventEmitter.Publish(events.DispatchFail, txHash, eventPayload); err != nil {
if err := cu.Store.CreateDispatchStatus(ctx, payload.OtxId, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
dispatchStatus.Status = enum.IN_NETWORK
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.EventEmitter.Publish(events.DispatchSuccess, txHash, eventPayload); err != nil {
if err := cu.Store.CreateDispatchStatus(ctx, payload.OtxId, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}

View File

@ -3,59 +3,53 @@ package task
import (
"context"
"encoding/json"
"fmt"
"math/big"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
type (
TransferPayload struct {
TrackingId string `json:"trackingId"`
From string `json:"from" `
To string `json:"to"`
VoucherAddress string `json:"voucherAddress"`
Amount uint64 `json:"amount"`
}
transferEventPayload struct {
DispatchTaskId string `json:"dispatchTaskId"`
OTXId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}
)
type TransferPayload struct {
TrackingId string `json:"trackingId"`
From string `json:"from" `
To string `json:"to"`
VoucherAddress string `json:"voucherAddress"`
Amount uint64 `json:"amount"`
}
func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
payload TransferPayload
err error
networkBalance big.Int
payload TransferPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
cu.SystemContainer.LockPrefix+payload.From,
cu.SystemContainer.LockTimeout,
nil,
lockPrefix+payload.From,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
key, err := cu.Keystore.LoadPrivateKey(ctx, payload.From)
key, err := cu.Store.LoadPrivateKey(ctx, payload.From)
if err != nil {
return err
}
@ -66,26 +60,28 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, payload.From); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["transfer"].EncodeArgs(w3.A(payload.To), new(big.Int).SetUint64(payload.Amount))
input, err := cu.Abis[custodial.Transfer].EncodeArgs(
celoutils.HexToAddress(payload.To),
new(big.Int).SetUint64(payload.Amount),
)
if err != nil {
return err
}
// TODO: Review gas params.
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
key,
celoutils.ContractExecutionTxOpts{
ContractAddress: w3.A(payload.VoucherAddress),
ContractAddress: celoutils.HexToAddress(payload.VoucherAddress),
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
@ -98,14 +94,14 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.TRANSFER_VOUCHER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: payload.From,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
TransferValue: payload.Amount,
Nonce: builtTx.Nonce(),
@ -114,6 +110,13 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.Balance(celoutils.HexToAddress(payload.From), nil).Returns(&networkBalance),
); err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
@ -135,36 +138,29 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
}
gasRefillPayload, err := json.Marshal(AccountPayload{
PublicKey: payload.From,
PublicKey: payload.From,
TrackingId: payload.TrackingId,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
if !balanceCheck(networkBalance) {
if err := cu.Store.GasLock(ctx, payload.From); err != nil {
return err
}
eventPayload := &transferEventPayload{
OTXId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.EventEmitter.Publish(
events.SignTransfer,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
}
return nil

View File

@ -0,0 +1,196 @@
package task
import (
"context"
"encoding/json"
"math/big"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
type TransferAuthPayload struct {
Amount uint64 `json:"amount"`
Authorizer string `json:"authorizer"`
AuthorizedAddress string `json:"authorizedAddress"`
TrackingId string `json:"trackingId"`
VoucherAddress string `json:"voucherAddress"`
}
func SignTransferAuthorizationProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
networkBalance big.Int
payload TransferAuthPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+payload.Authorizer,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
key, err := cu.Store.LoadPrivateKey(ctx, payload.Authorizer)
if err != nil {
return err
}
nonce, err := cu.Noncestore.Acquire(ctx, payload.Authorizer)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, payload.Authorizer); nErr != nil {
err = nErr
}
}
}()
input, err := cu.Abis[custodial.Approve].EncodeArgs(
celoutils.HexToAddress(payload.AuthorizedAddress),
new(big.Int).SetUint64(payload.Amount),
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
key,
celoutils.ContractExecutionTxOpts{
ContractAddress: celoutils.HexToAddress(payload.VoucherAddress),
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.TRANSFER_AUTH,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
TransferValue: 0,
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.Balance(celoutils.HexToAddress(payload.Authorizer), nil).Returns(&networkBalance),
); err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
// Auto-revoke every session (15 min)
// Check if already a revoke request
if payload.Amount > 0 {
taskPayload, err := json.Marshal(TransferAuthPayload{
TrackingId: payload.TrackingId,
Amount: 0,
Authorizer: payload.Authorizer,
AuthorizedAddress: payload.AuthorizedAddress,
VoucherAddress: payload.VoucherAddress,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.SignTransferTaskAuth,
tasker.DefaultPriority,
&tasker.Task{
Payload: taskPayload,
},
asynq.ProcessIn(cu.ApprovalTimeout),
)
if err != nil {
return err
}
}
gasRefillPayload, err := json.Marshal(AccountPayload{
PublicKey: payload.Authorizer,
TrackingId: payload.TrackingId,
})
if err != nil {
return err
}
if !balanceCheck(networkBalance) {
if err := cu.Store.GasLock(ctx, payload.Authorizer); err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
}
return nil
}
}

View File

@ -0,0 +1,37 @@
package task
import (
"math/big"
"time"
"github.com/bsm/redislock"
)
const (
lockPrefix = "lock:"
lockRetryDelay = 25 * time.Millisecond
lockTimeout = 1 * time.Second
)
var (
// 20 gwei = max gas price we are willing to pay
// 250k = max gas limit
// minGasBalanceRequired is optimistic that the immidiate next transfer request will be successful
// but the subsequent one could fail (though low probability), therefore we can trigger a gas lock.
// Therefore our system wide threshold is 0.01 CELO or 10000000000000000 gas units
minGasBalanceRequired = big.NewInt(20000000000 * 250000 * 2)
)
// lockRetry will at most try to obtain the lock 20 times within ~0.5s.
// it is expected to prevent immidiate requeue of the task at the expense of more redis calls.
func lockRetry() redislock.RetryStrategy {
return redislock.LimitRetry(
redislock.LinearBackoff(lockRetryDelay),
20,
)
}
// balanceCheck compares the network balance with the system set min as threshold to execute a transfer.
func balanceCheck(networkBalance big.Int) bool {
return minGasBalanceRequired.Cmp(&networkBalance) < 0
}

View File

@ -1,13 +1,7 @@
package tasker
import (
"crypto/ecdsa"
"encoding/json"
"math/big"
"time"
"github.com/celo-org/celo-blockchain/common"
"github.com/grassrootseconomics/w3-celo-patch"
)
type (
@ -15,36 +9,17 @@ type (
TaskName string
)
type SystemContainer struct {
Abis map[string]*w3.Func
AccountIndexContract common.Address
GasFaucetContract common.Address
GasRefillThreshold *big.Int
GasRefillValue *big.Int
GiftableGasValue *big.Int
GiftableToken common.Address
GiftableTokenValue *big.Int
LockPrefix string
LockTimeout time.Duration
PrivateKey *ecdsa.PrivateKey
PublicKey string
TokenDecimals int
TokenTransferGasLimit uint64
}
type Task struct {
Id string `json:"id"`
Payload json.RawMessage `json:"payload"`
}
const (
AccountPrepareTask TaskName = "sys:prepare_account"
AccountRegisterTask TaskName = "sys:register_account"
AccountGiftGasTask TaskName = "sys:gift_gas"
AccountGiftVoucherTask TaskName = "sys:gift_token"
AccountRefillGasTask TaskName = "sys:refill_gas"
SignTransferTask TaskName = "usr:sign_transfer"
DispatchTxTask TaskName = "rpc:dispatch"
AccountRegisterTask TaskName = "sys:register_account"
AccountRefillGasTask TaskName = "sys:refill_gas"
SignTransferTask TaskName = "usr:sign_transfer"
SignTransferTaskAuth TaskName = "usr:sign_transfer_auth"
DispatchTxTask TaskName = "rpc:dispatch"
)
const (

View File

@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS keystore (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
public_key TEXT NOT NULL,
private_key TEXT NOT NULL,
active BOOLEAN DEFAULT true,
active BOOLEAN DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
);
CREATE INDEX IF NOT EXISTS public_key_idx ON keystore(public_key);

View File

@ -12,7 +12,7 @@ INSERT INTO otx_tx_type (value) VALUES
-- Origin tx table
CREATE TABLE IF NOT EXISTS otx_sign (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tracking_id TEXT NOT NULL,
tracking_id uuid NOT NULL,
"type" TEXT REFERENCES otx_tx_type(value) NOT NULL,
raw_tx TEXT NOT NULL,
tx_hash TEXT NOT NULL,
@ -24,8 +24,9 @@ CREATE TABLE IF NOT EXISTS otx_sign (
nonce int NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS tx_hash_idx ON otx_sign USING hash(tx_hash);
CREATE INDEX IF NOT EXISTS from_idx ON otx_sign USING hash("from");
CREATE INDEX IF NOT EXISTS tracking_id_idx ON otx_sign (tracking_id);
CREATE INDEX IF NOT EXISTS tx_hash_idx ON otx_sign (tx_hash);
CREATE INDEX IF NOT EXISTS from_idx ON otx_sign ("from");
-- Otx dispatch status enum table
-- Enforces referential integrity on the dispatch table

View File

@ -0,0 +1,28 @@
-- Gas quota meta table
CREATE TABLE IF NOT EXISTS gas_quota_meta (
default_quota INT NOT NULL
);
INSERT INTO gas_quota_meta (default_quota) VALUES (25);
-- Gas quota table
CREATE TABLE IF NOT EXISTS gas_quota (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
key_id INT REFERENCES keystore(id) NOT NULL,
quota INT NOT NULL DEFAULT 0
);
-- Gas quota trigger on keystore insert to default 0 quota
-- We wait for the event handler to correctly set the chain quota
create function insert_gas_quota()
returns trigger
as $$
begin
insert into gas_quota (key_id) values (new.id);
return new;
end;
$$ language plpgsql;
create trigger insert_gas_quota
after insert on keystore
for each row
execute procedure insert_gas_quota();

View File

@ -0,0 +1,33 @@
ALTER TABLE keystore
ADD COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE otx_dispatch
ADD COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE gas_quota
ADD COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- updated_at function
create function update_timestamp()
returns trigger
as $$
begin
new.updated_at = current_timestamp;
return new;
end;
$$ language plpgsql;
create trigger update_keystore_timestamp
before update on keystore
for each row
execute procedure update_timestamp();
create trigger update_otx_dispatch_timestamp
before update on otx_dispatch
for each row
execute procedure update_timestamp();
create trigger update_gas_quota_timestamp
before update on gas_quota
for each row
execute procedure update_timestamp();

View File

@ -0,0 +1,34 @@
-- Replace gas_quota with gas_lock which checks network balance threshold
DROP TRIGGER IF EXISTS update_gas_quota_timestamp ON gas_quota;
DROP TABLE IF EXISTS gas_quota_meta;
DROP TABLE IF EXISTS gas_quota;
DROP TRIGGER IF EXISTS insert_gas_quota ON keystore;
DROP FUNCTION IF EXISTS insert_gas_quota;
-- Gas lock table
-- A gas_locked account indicates gas balance is below threshold awaiting next available top up
CREATE TABLE IF NOT EXISTS gas_lock (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
key_id INT REFERENCES keystore(id) NOT NULL,
lock BOOLEAN DEFAULT true,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
create function insert_gas_lock()
returns trigger
as $$
begin
insert into gas_lock (key_id) values (new.id);
return new;
end;
$$ language plpgsql;
create trigger insert_gas_lock
after insert on keystore
for each row
execute procedure insert_gas_lock();
create trigger update_gas_lock_timestamp
before update on gas_lock
for each row
execute procedure update_timestamp();

View File

@ -0,0 +1 @@
INSERT INTO otx_tx_type (value) VALUES ('TRANSFER_AUTHORIZATION');

View File

@ -19,9 +19,8 @@ const (
FAIL_UNKNOWN_RPC_ERROR OtxStatus = "FAIL_UNKNOWN_RPC_ERROR"
REVERTED OtxStatus = "REVERTED"
GIFT_GAS OtxType = "GIFT_GAS"
ACCOUNT_REGISTER OtxType = "ACCOUNT_REGISTER"
GIFT_VOUCHER OtxType = "GIFT_VOUCHER"
REFILL_GAS OtxType = "REFILL_GAS"
TRANSFER_AUTH OtxType = "TRANSFER_AUTHORIZATION"
TRANSFER_VOUCHER OtxType = "TRANSFER_VOUCHER"
)

View File

@ -13,21 +13,21 @@ func AsynqCompatibleLogger(lo logf.Logger) AsynqLogger {
}
func (l AsynqLogger) Debug(args ...interface{}) {
l.Lo.Debug("asynq", "debug", args[0])
l.Lo.Debug("asynq: server", "msg", args[0])
}
func (l AsynqLogger) Info(args ...interface{}) {
l.Lo.Info("asynq", "info", args[0])
l.Lo.Info("asynq: server", "msg", args[0])
}
func (l AsynqLogger) Warn(args ...interface{}) {
l.Lo.Warn("asynq", "warn", args[0])
l.Lo.Warn("asynq: server", "msg", args[0])
}
func (l AsynqLogger) Error(args ...interface{}) {
l.Lo.Error("asynq", "error", args[0])
l.Lo.Error("asynq: server", "msg", args[0])
}
func (l AsynqLogger) Fatal(args ...interface{}) {
l.Lo.Fatal("asynq", "fatal", args[0])
l.Lo.Fatal("asynq: server", "msg", args[0])
}

View File

@ -1,56 +0,0 @@
package postgres
import (
"context"
"os"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/tern/v2/migrate"
)
const (
schemaTable = "schema_version"
)
type PostgresPoolOpts struct {
DSN string
MigrationsFolderPath string
}
// NewPostgresPool creates a reusbale connection pool across the cic-custodial component.
func NewPostgresPool(o PostgresPoolOpts) (*pgxpool.Pool, error) {
parsedConfig, err := pgxpool.ParseConfig(o.DSN)
if err != nil {
return nil, err
}
dbPool, err := pgxpool.NewWithConfig(context.Background(), parsedConfig)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := dbPool.Acquire(ctx)
if err != nil {
return nil, err
}
defer conn.Release()
migrator, err := migrate.NewMigrator(ctx, conn.Conn(), schemaTable)
if err != nil {
return nil, err
}
if err := migrator.LoadMigrations(os.DirFS(o.MigrationsFolderPath)); err != nil {
return nil, err
}
if err := migrator.Migrate(ctx); err != nil {
return nil, err
}
return dbPool, nil
}

View File

@ -2,23 +2,29 @@ package redis
import (
"context"
"time"
"github.com/go-redis/redis/v8"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/redis/go-redis/v9"
)
type RedisPoolOpts struct {
DSN string
MinIdleConns int
}
const (
systemGlobalLockKey = "system:global_lock"
)
type RedisPool struct {
Client *redis.Client
}
type (
RedisPoolOpts struct {
DSN string
MinIdleConns int
}
RedisPool struct {
Client *redis.Client
}
)
// NewRedisPool creates a reusable connection across the cic-custodial componenent.
// Note: Each db namespace requires its own connection pool.
func NewRedisPool(o RedisPoolOpts) (*RedisPool, error) {
func NewRedisPool(ctx context.Context, o RedisPoolOpts) (*RedisPool, error) {
redisOpts, err := redis.ParseURL(o.DSN)
if err != nil {
return nil, err
@ -28,11 +34,10 @@ func NewRedisPool(o RedisPoolOpts) (*RedisPool, error) {
redisClient := redis.NewClient(redisOpts)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ctx, cancel := context.WithTimeout(ctx, util.SLATimeout)
defer cancel()
_, err = redisClient.Ping(ctx).Result()
if err != nil {
if err := redisClient.SetNX(ctx, systemGlobalLockKey, false, 0).Err(); err != nil {
return nil, err
}

8
pkg/util/sla.go Normal file
View File

@ -0,0 +1,8 @@
package util
import "time"
const (
// SLATimeout is the max duration after which any network call/context should be aborted.
SLATimeout = 5 * time.Second
)

View File

@ -34,6 +34,18 @@ INSERT INTO otx_sign(
nonce
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id
--name: get-next-nonce
-- Gets last nonce from the otx table for a particular address for bootstrapping purposes
-- $1: public_key
SELECT nonce + 1 AS nonce FROM otx_sign WHERE otx_sign.from = $1 ORDER BY created_at DESC LIMIT 1;
--name: get-tx-status-by-tracking-id
-- Gets tx status's from possible multiple txs with the same tracking_id
-- $1: tracking_id
SELECT otx_sign.type, otx_sign.tx_hash, otx_sign.transfer_value, otx_sign.created_at, otx_dispatch.status FROM otx_sign
INNER JOIN otx_dispatch ON otx_sign.id = otx_dispatch.otx_id
WHERE otx_sign.tracking_id=$1
--name: create-dispatch-status
-- Create a new dispatch status
-- $1: otx_id
@ -43,7 +55,7 @@ INSERT INTO otx_dispatch(
"status"
) VALUES($1, $2) RETURNING id
--name: update-chain-status
--name: update-dispatch-status
-- Updates the status of the dispatched tx with the chain mine status
-- $1: tx_hash
-- $2: status
@ -51,15 +63,34 @@ INSERT INTO otx_dispatch(
UPDATE otx_dispatch SET "status" = $2, "block" = $3 WHERE otx_dispatch.id = (
SELECT otx_dispatch.id FROM otx_dispatch
INNER JOIN otx_sign ON otx_dispatch.otx_id = otx_sign.id
WHERE otx_sign.tx_hash = $1
WHERE otx_sign.tx_hash=$1
AND otx_dispatch.status = 'IN_NETWORK'
)
--name: get-tx-status-by-tracking-id
-- Gets tx status's from possible multiple txs with the same tracking_id
-- $1: tracking_id
SELECT otx_sign.type, otx_sign.tx_hash, otx_sign.transfer_value, otx_sign.created_at, otx_dispatch.status FROM otx_sign
INNER JOIN otx_dispatch ON otx_sign.id = otx_dispatch.otx_id
WHERE otx_sign.tracking_id = $1
--name: activate-account
-- Activate an account following successful quorum
-- $1: public_key
UPDATE keystore SET active = true WHERE public_key=$1
-- TODO: Scroll by status type with cursor pagination
--name: get-account-status-by-address
-- Gets current gas lock and activation status for an individual account by address
-- $1: public_key
SELECT keystore.active, gas_lock.lock FROM keystore
INNER JOIN gas_lock ON keystore.id = gas_lock.key_id
WHERE keystore.public_key=$1
--name: acc-gas-lock
-- Locks an account for gas reasons
-- $1: public_key
UPDATE gas_lock SET lock = true WHERE key_id = (
SELECT id FROM keystore
WHERE public_key=$1
)
--name: acc-gas-unlock
-- Unlocks an account for gas reasons
-- $1: public_key
UPDATE gas_lock SET lock = false WHERE key_id = (
SELECT id FROM keystore
WHERE public_key=$1
)