From b19bf34e5f711d96a7fcae128fdda4ca471a484f Mon Sep 17 00:00:00 2001 From: Anton Nesterov Date: Sat, 31 Aug 2024 16:46:20 +0200 Subject: [PATCH] [init] --- .air.toml | 44 + .gitignore | 7 + .gitlab-ci.yml | 152 ++ .helm/.gitkeep | 0 .helm/Chart.yaml | 5 + .helm/templates/_helpers.tpl | 32 + .helm/templates/configmap.yaml | 14 + .helm/templates/deployment.yaml | 95 + .helm/templates/ingress.yaml | 34 + .helm/templates/poddisruptionbudget.yaml | 16 + .helm/templates/psp.yaml | 46 + .helm/templates/rbac.yaml | 30 + .helm/templates/secret.yaml | 15 + .helm/templates/service.yaml | 42 + .helm/templates/serviceaccount.yaml | 10 + .helm/values-agent.yaml | 128 ++ .helm/values-rendering.yaml | 127 ++ .helm/values.yaml | 0 Dockerfile | 11 + Makefile | 34 + cmd/envcrypt.html | 559 ++++++ cmd/keypair/keypair_test.go | 51 + cmd/keypair/main.go | 96 + cmd/prebuild/main.go | 58 + dev.env | 16 + docker-compose.yaml | 44 + docs/docs.go | 2069 ++++++++++++++++++++++ docs/swagger.json | 2040 +++++++++++++++++++++ docs/swagger.yaml | 1279 +++++++++++++ go.mod | 104 ++ go.sum | 373 ++++ keys/private.pem | 28 + keys/private.rsa | 1 + keys/public.pem | 9 + keys/public.rsa | 1 + main.go | 29 + pkg/crypto/keypair.go | 93 + pkg/crypto/rsa.go | 50 + pkg/crypto/utils.go | 32 + pkg/eth/eth.go | 183 ++ pkg/eth/eth_account.go | 116 ++ pkg/eth/eth_approve.go | 64 + pkg/eth/eth_balance.go | 107 ++ pkg/eth/eth_block.go | 37 + pkg/eth/eth_transaction.go | 138 ++ pkg/eth/eth_util.go | 17 + pkg/eth/eth_withdraw.go | 230 +++ pkg/locker/keys.go | 4 + pkg/locker/settings.go | 38 + pkg/locker/types.go | 39 + pkg/rest/account_v1.go | 18 + pkg/rest/account_v1_balance.go | 153 ++ pkg/rest/account_v1_count.go | 41 + pkg/rest/account_v1_create.go | 123 ++ pkg/rest/account_v1_list.go | 61 + pkg/rest/account_v1_spender.go | 62 + pkg/rest/auth_middleware.go | 21 + pkg/rest/echo_v1.go | 18 + pkg/rest/eth_v1.go | 33 + pkg/rest/eth_v1_account.go | 76 + pkg/rest/eth_v1_approve.go | 148 ++ pkg/rest/eth_v1_balance.go | 85 + pkg/rest/eth_v1_custodial.go | 84 + pkg/rest/eth_v1_recharge.go | 63 + pkg/rest/eth_v1_transaction.go | 62 + pkg/rest/eth_v1_withdraw.go | 230 +++ pkg/rest/export_v1.go | 144 ++ pkg/rest/routes.go | 37 + pkg/rest/sync_middleware.go | 30 + pkg/rest/tron_v1.go | 40 + pkg/rest/tron_v1_account.go | 76 + pkg/rest/tron_v1_approve.go | 148 ++ pkg/rest/tron_v1_balance.go | 85 + pkg/rest/tron_v1_custodial.go | 84 + pkg/rest/tron_v1_recharge.go | 63 + pkg/rest/tron_v1_transaction.go | 66 + pkg/rest/tron_v1_withdraw.go | 224 +++ pkg/store/account.go | 173 ++ pkg/store/db.go | 60 + pkg/store/eth_account.go | 32 + pkg/store/trx_account.go | 32 + pkg/store/webhook.go | 79 + pkg/tron/tron.go | 176 ++ pkg/tron/tron_account.go | 72 + pkg/tron/tron_approve.go | 42 + pkg/tron/tron_balance.go | 112 ++ pkg/tron/tron_block.go | 34 + pkg/tron/tron_transaction.go | 91 + pkg/tron/tron_util.go | 31 + pkg/tron/tron_withdraw.go | 184 ++ readme.md | 148 ++ 91 files changed, 12288 insertions(+) create mode 100644 .air.toml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .helm/.gitkeep create mode 100644 .helm/Chart.yaml create mode 100644 .helm/templates/_helpers.tpl create mode 100644 .helm/templates/configmap.yaml create mode 100644 .helm/templates/deployment.yaml create mode 100644 .helm/templates/ingress.yaml create mode 100644 .helm/templates/poddisruptionbudget.yaml create mode 100644 .helm/templates/psp.yaml create mode 100644 .helm/templates/rbac.yaml create mode 100644 .helm/templates/secret.yaml create mode 100644 .helm/templates/service.yaml create mode 100644 .helm/templates/serviceaccount.yaml create mode 100644 .helm/values-agent.yaml create mode 100644 .helm/values-rendering.yaml create mode 100644 .helm/values.yaml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cmd/envcrypt.html create mode 100644 cmd/keypair/keypair_test.go create mode 100644 cmd/keypair/main.go create mode 100644 cmd/prebuild/main.go create mode 100644 dev.env create mode 100644 docker-compose.yaml create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 keys/private.pem create mode 100644 keys/private.rsa create mode 100644 keys/public.pem create mode 100644 keys/public.rsa create mode 100644 main.go create mode 100644 pkg/crypto/keypair.go create mode 100644 pkg/crypto/rsa.go create mode 100644 pkg/crypto/utils.go create mode 100644 pkg/eth/eth.go create mode 100644 pkg/eth/eth_account.go create mode 100644 pkg/eth/eth_approve.go create mode 100644 pkg/eth/eth_balance.go create mode 100644 pkg/eth/eth_block.go create mode 100644 pkg/eth/eth_transaction.go create mode 100644 pkg/eth/eth_util.go create mode 100644 pkg/eth/eth_withdraw.go create mode 100644 pkg/locker/keys.go create mode 100644 pkg/locker/settings.go create mode 100644 pkg/locker/types.go create mode 100644 pkg/rest/account_v1.go create mode 100644 pkg/rest/account_v1_balance.go create mode 100644 pkg/rest/account_v1_count.go create mode 100644 pkg/rest/account_v1_create.go create mode 100644 pkg/rest/account_v1_list.go create mode 100644 pkg/rest/account_v1_spender.go create mode 100644 pkg/rest/auth_middleware.go create mode 100644 pkg/rest/echo_v1.go create mode 100644 pkg/rest/eth_v1.go create mode 100644 pkg/rest/eth_v1_account.go create mode 100644 pkg/rest/eth_v1_approve.go create mode 100644 pkg/rest/eth_v1_balance.go create mode 100644 pkg/rest/eth_v1_custodial.go create mode 100644 pkg/rest/eth_v1_recharge.go create mode 100644 pkg/rest/eth_v1_transaction.go create mode 100644 pkg/rest/eth_v1_withdraw.go create mode 100644 pkg/rest/export_v1.go create mode 100644 pkg/rest/routes.go create mode 100644 pkg/rest/sync_middleware.go create mode 100644 pkg/rest/tron_v1.go create mode 100644 pkg/rest/tron_v1_account.go create mode 100644 pkg/rest/tron_v1_approve.go create mode 100644 pkg/rest/tron_v1_balance.go create mode 100644 pkg/rest/tron_v1_custodial.go create mode 100644 pkg/rest/tron_v1_recharge.go create mode 100644 pkg/rest/tron_v1_transaction.go create mode 100644 pkg/rest/tron_v1_withdraw.go create mode 100644 pkg/store/account.go create mode 100644 pkg/store/db.go create mode 100644 pkg/store/eth_account.go create mode 100644 pkg/store/trx_account.go create mode 100644 pkg/store/webhook.go create mode 100644 pkg/tron/tron.go create mode 100644 pkg/tron/tron_account.go create mode 100644 pkg/tron/tron_approve.go create mode 100644 pkg/tron/tron_balance.go create mode 100644 pkg/tron/tron_block.go create mode 100644 pkg/tron/tron_transaction.go create mode 100644 pkg/tron/tron_util.go create mode 100644 pkg/tron/tron_withdraw.go create mode 100644 readme.md diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..1a53f87 --- /dev/null +++ b/.air.toml @@ -0,0 +1,44 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "./tmp/main" +cmd = "go build -o ./tmp/main ." +delay = 0 +exclude_dir = ["assets", "tmp", "vendor", "testdata", "solidity"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +full_bin = "" +include_dir = [] +include_ext = ["go", "tpl", "tmpl", "html"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = false + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +time = false + +[misc] +clean_on_exit = false + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14db108 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.env +bin/ +build/ +tmp/ +node_modules/ +dev-database.sqlite +dev.sqlite diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c09056a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,152 @@ +services: + - name: docker:dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + +variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + DOMAIN: ${DOMAIN} + IMAGE_TAG: ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} + +stages: + - build + - deploy + +.build-images: + stage: build + before_script: + script: + - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} + - echo ${ENV} | base64 -d > .env + - export BR=$(echo $CI_COMMIT_REF_NAME | tr / -) + - docker build --tag ${IMAGE_TAG}:${BR}${CI_COMMIT_SHORT_SHA} --tag ${IMAGE_TAG} . + - docker push ${IMAGE_TAG}:${BR}${CI_COMMIT_SHORT_SHA} + - docker push ${IMAGE_TAG} + +.deploy-helm: + stage: deploy + image: + name: dtzar/helm-kubectl + script: + - mkdir -p ~/.kube && echo ${KUBE_CONFIG} | base64 -d > ~/.kube/config + - helm repo add --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASS lpm https://$CI_REGISTRY_HELM/stable + - envsubst < ./.helm/values-rendering.yaml > ./.helm/values.yaml + - export BR=$(echo $CI_COMMIT_REF_NAME | tr / -) + - helm template ./.helm -f ./.helm/values.yaml + - helm upgrade --install -n ${CI_PROJECT_NAMESPACE} ${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME} ./.helm -f ./.helm/values.yaml + --set image.repository=${IMAGE_TAG} + --set image.tag=${BR}${CI_COMMIT_SHORT_SHA} + --set configs.COMMIT_HASH=${CI_COMMIT_SHA} + --set ingress.enabled=true + --set ingress.hosts=${BR}-${CI_PROJECT_NAME}.dev.${DOMAIN} + --set "ingress.tls=true" + - echo ${BR}-${CI_PROJECT_NAME}.${DOMAIN} + +.deploy-helm-agent: + stage: deploy + image: + name: dtzar/helm-kubectl + script: + - mkdir -p ~/.kube && echo ${KUBE_CONFIG} | base64 -d > ~/.kube/config + - helm repo add --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASS lpm https://$CI_REGISTRY_HELM/stable + - envsubst < ./.helm/values-agent.yaml > ./.helm/values.yaml + - export BR=$(echo $CI_COMMIT_REF_NAME | tr / -) + - helm template ./.helm -f ./.helm/values.yaml + - helm upgrade --install -n ${CI_PROJECT_NAMESPACE} ${CI_PROJECT_NAME}-${CI_COMMIT_REF_NAME}-agent ./.helm -f ./.helm/values.yaml + --set image.repository=${IMAGE_TAG} + --set image.tag=${BR}${CI_COMMIT_SHORT_SHA} + --set configs.COMMIT_HASH=${CI_COMMIT_SHA} + --set ingress.enabled=false + - echo ${BR}-${CI_PROJECT_NAME}.${DOMAIN} +.deploy-helm-prod: + stage: deploy + image: + name: dtzar/helm-kubectl + script: + - mkdir -p ~/.kube && echo ${KUBE_CONFIG} | base64 -d > ~/.kube/config + - helm repo add --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASS lpm https://$CI_REGISTRY_HELM/stable + - envsubst < ./.helm/values-rendering.yaml > ./.helm/values.yaml + - cat ./.helm/values.yaml + - export BR=$(echo $CI_COMMIT_REF_NAME | tr / -) + - helm template ./.helm -f ./.helm/values.yaml + - sed -i 's/develop/'${BR}${CI_COMMIT_SHORT_SHA}'/' ./.helm/templates/job-migrate.yaml + - helm upgrade --install -n crypto-stories ${CI_PROJECT_NAME} ./.helm -f ./.helm/values.yaml + --set image.repository=${IMAGE_TAG} + --set image.tag=${BR}${CI_COMMIT_SHORT_SHA} + --set configs.COMMIT_HASH=${CI_COMMIT_SHA} + --set ingress.enabled=false + --set ingress.hosts=custodial.cryptopay.is + --set "ingress.tls=false" + - echo trc.cryptopay.is + +build-images-develop: + extends: .build-images + environment: develop + tags: + - k8s-runner01 + only: + - develop + +build-images-feature: + extends: .build-images + environment: feature + tags: + - k8s-runner01 + only: + - /^feature/ + +build-images-production: + extends: .build-images + environment: production + tags: + - k8s-runner01 + only: + - rc + - tags + +deploy-develop: + extends: .deploy-helm + environment: develop + tags: + - k8s-runner01 + only: + - develop + when: manual +deploy-develop-agent: + extends: .deploy-helm-agent + environment: develop + tags: + - k8s-runner01 + only: + - develop + when: manual + +deploy-feature: + extends: .deploy-helm + environment: feature + tags: + - k8s-runner01 + only: + - /^feature/ + when: manual + +deploy-feature-agent: + extends: .deploy-helm-agent + environment: feature + tags: + - k8s-runner01 + only: + - /^feature/ + when: manual + +deploy-production: + extends: .deploy-helm-prod + environment: production + tags: + - k8s-runner01 + only: + - rc + - tags + when: manual diff --git a/.helm/.gitkeep b/.helm/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.helm/Chart.yaml b/.helm/Chart.yaml new file mode 100644 index 0000000..75a59fa --- /dev/null +++ b/.helm/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: 0.0.1 +description: lpm +name: lpm +version: 0.0.1 diff --git a/.helm/templates/_helpers.tpl b/.helm/templates/_helpers.tpl new file mode 100644 index 0000000..b4df1d7 --- /dev/null +++ b/.helm/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{- define "service.name" -}} +{{- default .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "service.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- define "podSecurityPolicy.apiVersion" -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "extensions/v1beta1" -}} +{{- end -}} +{{- end -}} +{{- define "service.chart" -}} +{{- printf "%s-%s" .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "service.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/.helm/templates/configmap.yaml b/.helm/templates/configmap.yaml new file mode 100644 index 0000000..996e924 --- /dev/null +++ b/.helm/templates/configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.configs.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "service.fullname" . }}-configmaps + annotations: + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +data: + {{- range $key, $value := .Values.configs }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end -}} diff --git a/.helm/templates/deployment.yaml b/.helm/templates/deployment.yaml new file mode 100644 index 0000000..ab8c2d2 --- /dev/null +++ b/.helm/templates/deployment.yaml @@ -0,0 +1,95 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "service.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 50% + maxUnavailable: 50% + selector: + matchLabels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} + annotations: + {{ toYaml .Values.podAnnotations | indent 8 }} + spec: + serviceAccountName: {{ include "service.fullname" . }} + imagePullSecrets: + - name: docker-registry + containers: + - name: {{ include "service.fullname" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + command: {{ .Values.command.cli }} + args: {{ .Values.command.args }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: +{{ toYaml .Values.securityContext | indent 12 }} + ports: + - name: http + containerPort: {{ .Values.containerPort }} + protocol: TCP + envFrom: + {{- if .Values.configs.enabled }} + - configMapRef: + name: {{ include "service.fullname" . }}-configmaps + {{- end }} + {{- if .Values.secrets.enabled }} + - secretRef: + name: {{ include "service.fullname" . }}-secrets + {{- end }} + {{- if .Values.livenessProbe }} + livenessProbe: +{{ toYaml .Values.livenessProbe | indent 12 }} + {{- end }} + {{- if .Values.readinessProbe }} + readinessProbe: +{{ toYaml .Values.readinessProbe | indent 12 }} + {{- end }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- if .Values.secretfile.enabled }} + volumeMounts: + - name: {{ include ".Values.secretfile.name" . }} + mountPath: ${{ .Values.secretfile.path }} + {{- end }} + {{- if .Values.secretfile.enabled }} + volumes: + - name: {{ include ".Values.secretfile.name" . }} + secret: + secretName: {{ include ".Values.secretfile.name" . }} + {{- end }} + {{- if .Values.configfile.enabled }} + volumeMounts: + - name: {{ include "service.fullname" . }} + mountPath: ${{ .Values.configfile.path }} + {{- end }} + {{- if .Values.configfile.enabled }} + volumes: + - name: {{ include "service.fullname" . }} + configMap: + name: {{ include "service.fullname" . }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} diff --git a/.helm/templates/ingress.yaml b/.helm/templates/ingress.yaml new file mode 100644 index 0000000..f4891b6 --- /dev/null +++ b/.helm/templates/ingress.yaml @@ -0,0 +1,34 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "service.fullname" . }} +{{- $httpPort := .Values.service.httpPort }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} + name: {{ template "service.fullname" . }} + namespace: {{ .Release.Namespace | quote }} +spec: + rules: + - host: {{ .Values.ingress.hosts }} + http: + paths: + - backend: + service: + name: {{ $fullName }} + port: + number: {{ $httpPort }} + path: / + pathType: ImplementationSpecific +{{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ .Values.ingress.hosts }} + secretName: {{ .Values.ingress.secretName | default (printf "%s-tls" (include "service.fullname" .)) }} +{{- end }} +{{- end -}} diff --git a/.helm/templates/poddisruptionbudget.yaml b/.helm/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..b919fdb --- /dev/null +++ b/.helm/templates/poddisruptionbudget.yaml @@ -0,0 +1,16 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "service.controller.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +spec: + selector: + matchLabels: + app: {{ template "service.name" . }} + release: "{{ .Release.Name }}" + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} +{{- end }} diff --git a/.helm/templates/psp.yaml b/.helm/templates/psp.yaml new file mode 100644 index 0000000..6d9cf38 --- /dev/null +++ b/.helm/templates/psp.yaml @@ -0,0 +1,46 @@ +{{- if and .Values.rbac.create .Values.rbac.pspEnabled }} +apiVersion: {{ template "podSecurityPolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "service.fullname" . }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +spec: + privileged: false + allowPrivilegeEscalation: false + requiredDropCapabilities: + - ALL + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + - 'persistentVolumeClaim' + - 'hostPath' + hostNetwork: true + hostIPC: false + hostPID: true + hostPorts: + - min: 0 + max: 65535 + runAsUser: + rule: 'MustRunAs' + ranges: + - min: 1001 + max: 1001 + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1001 + max: 1001 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1001 + max: 1001 + readOnlyRootFilesystem: false +{{- end }} diff --git a/.helm/templates/rbac.yaml b/.helm/templates/rbac.yaml new file mode 100644 index 0000000..48c7476 --- /dev/null +++ b/.helm/templates/rbac.yaml @@ -0,0 +1,30 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: {{ template "service.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +rules: + - apiGroups: [""] + resources: ["services", "pods", "endpoints", "configmaps"] + verbs: ["get","list"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: {{ template "service.fullname" . }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "service.fullname" . }} +subjects: + - name: {{ template "service.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + kind: serviceAccount +{{- end -}} diff --git a/.helm/templates/secret.yaml b/.helm/templates/secret.yaml new file mode 100644 index 0000000..029c324 --- /dev/null +++ b/.helm/templates/secret.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.secrets.enabled -}} +apiVersion: v1 +kind: Secrets +metadata: + name: {{ include "service.fullname" . }}-secrets + annotations: + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +data: + {{- range $key,$value := .Values.secrets.all }} + {{ $key }}: {{ $value }} + {{- end }} +{{- end -}} + diff --git a/.helm/templates/service.yaml b/.helm/templates/service.yaml new file mode 100644 index 0000000..6d3d71b --- /dev/null +++ b/.helm/templates/service.yaml @@ -0,0 +1,42 @@ +{{- if and .Values.service.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "service.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- range $key, $value := .Values.service.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +spec: + type: {{ .Values.service.type }} + {{- if (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) }} + {{- if hasKey .Values.service "externalTrafficPolicy" -}} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- end }} + {{- if eq .Values.service.type "LoadBalancer" }} + loadBalancerIP: {{ default "" .Values.service.loadBalancerIP | quote }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.httpPort }} +{{- if hasKey .Values.service "targetPort" }} + targetPort: {{ .Values.service.targetPort }} +{{- end }} + - name: ws + port: {{ .Values.servicews.httpPort }} +{{- if hasKey .Values.servicews "targetPort" }} + targetPort: {{ .Values.servicews.targetPort }} +{{- end }} +{{- if hasKey .Values.service "nodePort" }} + nodePort: {{ .Values.service.nodePort }} +{{- end }} + selector: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name | quote }} +{{- end -}} + diff --git a/.helm/templates/serviceaccount.yaml b/.helm/templates/serviceaccount.yaml new file mode 100644 index 0000000..9783af8 --- /dev/null +++ b/.helm/templates/serviceaccount.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "service.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ include "service.fullname" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/.helm/values-agent.yaml b/.helm/values-agent.yaml new file mode 100644 index 0000000..4fbb3f0 --- /dev/null +++ b/.helm/values-agent.yaml @@ -0,0 +1,128 @@ +replicaCount: 1 +consumerReplicaCount: 1 + +image: + repository: "" + tag: "dev" + pullPolicy: Always + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + name: service + +rbac: + create: false + +containerPort: 80 + +securityContext: + privileged: true + # runAsUser: 1000 + # fsGroup: 1000 + +command: + enabled: true + cli: '["/bin/bash", "-c"]' + args: '["/app/build/agent --env=production"]' + +migrate: + enabled: true + cli: '["/bin/bash", "-c"]' + args: '[""]' + +configfile: + enabled: false + config: | + +secretfile: + enabled: false + +### secrets env +secrets: + enabled: false + +configs: + enabled: true + WEBHOOK_URL: $WEBHOOK_URL + TRON_GRPC_NODE: $TRON_GRPC_NODE + TRC20_USDT_CONTRACT_ADDRESS: $TRC20_USDT_CONTRACT_ADDRESS + ERC20_USDT_CONTRACT_ADDRESS: $ERC20_USDT_CONTRACT_ADDRESS + ERC20_USDT_CONTRACT_DECIMALS: $ERC20_USDT_CONTRACT_DECIMALS + ETH_RPC_NODE: $ETH_RPC_NODE + DB_TYPE: $DB_TYPE + DB_CONNECTION_SETTINGS: $DB_CONNECTION_SETTINGS + PRIVATE__BIP39_MNEMONIC: $PRIVATE__BIP39_MNEMONIC + PRIVATE__API_MASTER_KEY: $PRIVATE__API_MASTER_KEY + +#resources: +# limits: +# cpu: 250m +# memory: 2048Mi +# requests: +# cpu: 100m +# memory: 2048Mi + +consumerLivenessProbe: {} +livenessProbe: + {} + # todo fix probe url + # httpGet: + # path: /api/ + # port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + +consumerReadinessProbe: {} +readinessProbe: + {} + # todo fix probe url + # httpGet: + # path: / + # port: http + # initialDelaySeconds: 15 + # periodSeconds: 10 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +service: + enabled: true + httpPort: 8000 + targetPort: 8080 + type: ClusterIP + +servicews: + enabled: false + httpPort: 8001 + targetPort: 3000 + type: ClusterIP + +ingress: + enabled: false + annotations: + cert-manager.io/cluster-issuer: "letsencrypt" + kubernetes.io/ingress.class: nginx + kubernetes.io/tls-acme: "true" + + hosts: + - + +podDisruptionBudget: + enabled: false + minAvailable: 1 +#cronjobPhpCronScheduler: +# schedule: "0 12 * * *" +# command: '["/bin/sh"]' +# args: +# - -c +# - >- +# php bin/console + +#cronjobSuccessfulJobsHistoryLimit: 5 +#cronjobFailedJobsHistoryLimit: 10 diff --git a/.helm/values-rendering.yaml b/.helm/values-rendering.yaml new file mode 100644 index 0000000..a26a187 --- /dev/null +++ b/.helm/values-rendering.yaml @@ -0,0 +1,127 @@ +replicaCount: 1 +consumerReplicaCount: 1 + +image: + repository: "" + tag: "dev" + pullPolicy: Always + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + name: service + +rbac: + create: false + +containerPort: 80 + +securityContext: + privileged: true + # runAsUser: 1000 + # fsGroup: 1000 + +command: + enabled: true + cli: '["/bin/bash", "-c"]' + args: '["/app/build/custodial --env=production"]' + +migrate: + enabled: true + cli: '["/bin/bash", "-c"]' + args: '[""]' + +configfile: + enabled: false + config: | + +secretfile: + enabled: false + +### secrets env +secrets: + enabled: false + +configs: + enabled: true + TRON_GRPC_NODE: $TRON_GRPC_NODE + TRC20_USDT_CONTRACT_ADDRESS: $TRC20_USDT_CONTRACT_ADDRESS + ERC20_USDT_CONTRACT_ADDRESS: $ERC20_USDT_CONTRACT_ADDRESS + ERC20_USDT_CONTRACT_DECIMALS: $ERC20_USDT_CONTRACT_DECIMALS + ETH_RPC_NODE: $ETH_RPC_NODE + DB_TYPE: $DB_TYPE + DB_CONNECTION_SETTINGS: $DB_CONNECTION_SETTINGS + PRIVATE__BIP39_MNEMONIC: $PRIVATE__BIP39_MNEMONIC + PRIVATE__API_MASTER_KEY: $PRIVATE__API_MASTER_KEY + +#resources: +# limits: +# cpu: 250m +# memory: 2048Mi +# requests: +# cpu: 100m +# memory: 2048Mi + +consumerLivenessProbe: {} +livenessProbe: + {} + # todo fix probe url + # httpGet: + # path: /api/ + # port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + +consumerReadinessProbe: {} +readinessProbe: + {} + # todo fix probe url + # httpGet: + # path: / + # port: http + # initialDelaySeconds: 15 + # periodSeconds: 10 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +service: + enabled: true + httpPort: 8000 + targetPort: 8080 + type: ClusterIP + +servicews: + enabled: false + httpPort: 8001 + targetPort: 3000 + type: ClusterIP + +ingress: + enabled: false + annotations: + cert-manager.io/cluster-issuer: "letsencrypt" + kubernetes.io/ingress.class: nginx + kubernetes.io/tls-acme: "true" + + hosts: + - + +podDisruptionBudget: + enabled: false + minAvailable: 1 +#cronjobPhpCronScheduler: +# schedule: "0 12 * * *" +# command: '["/bin/sh"]' +# args: +# - -c +# - >- +# php bin/console + +#cronjobSuccessfulJobsHistoryLimit: 5 +#cronjobFailedJobsHistoryLimit: 10 diff --git a/.helm/values.yaml b/.helm/values.yaml new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1e1e948 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.21.3-bookworm +RUN apt update && apt install -y build-essential make + +WORKDIR /app +COPY . . +RUN go mod download +RUN make build-tools +RUN go build -o build/custodial main.go +RUN go build -o build/agent agent/main.go + +CMD [ "./custodial", "--env=production" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..23e2b38 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +uname := $(shell uname) + +test: + go test -v ./cmd/keypair + +build-tools: + GOOS=linux GOARCH=amd64 go build -o bin/keypair cmd/keypair/main.go + GOOS=linux GOARCH=arm64 go build -o bin/keypair.arm64 cmd/keypair/main.go + GOOS=darwin GOARCH=arm64 go build -o bin/keypair.m2 cmd/keypair/main.go + GOOS=darwin GOARCH=amd64 go build -o bin/keypair.macos cmd/keypair/main.go + GOOS=windows GOARCH=amd64 go build -o bin/keypair.exe cmd/keypair/main.go + +build: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/custodial main.go + chmod +x build/custodial + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/agent agent/main.go + chmod +x build/agent + #GOOS=windows GOARCH=amd64 go build -o build/custodial.exe main.go + #GOOS=darwin GOARCH=amd64 go build -o build/custodial.macos main.go + +setup: + curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin + go install github.com/swaggo/swag/cmd/swag@latest +# ifeq ($(uname), Linux) +# @echo "Linux: trying to install ubuntu dependencies" +# sudo apt-get install build-essential +# endif + + +develop: + air -c .air.toml + +doc: + swag init diff --git a/cmd/envcrypt.html b/cmd/envcrypt.html new file mode 100644 index 0000000..cc92d9d --- /dev/null +++ b/cmd/envcrypt.html @@ -0,0 +1,559 @@ + + + + ENVCrypt - env encrypt tool + + + + + + + + + + + \ No newline at end of file diff --git a/cmd/keypair/keypair_test.go b/cmd/keypair/keypair_test.go new file mode 100644 index 0000000..ce11425 --- /dev/null +++ b/cmd/keypair/keypair_test.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "testing" + + "custodial/pkg/crypto" +) + +func TestKeyPair(t *testing.T) { + kp, _ := crypto.KeyPair{}.Random() + + fmt.Println("\nPublic key base64: ") + fmt.Println(kp.PublicKeyBase64()) + + fmt.Println("\nPrivate key base64: ") + fmt.Println(kp.PrivateKeyBase64()) + + fmt.Println("\n Test encryption: ") + + text := "Hello world!" + pubKey := crypto.RSAKey(kp.PublicKeyBase64()) + encrypted := pubKey.EncryptToString(text) + + fmt.Println(encrypted) + + fmt.Println("\n Test decryption: ") + privKey := crypto.RSAKey(kp.PrivateKeyBase64()) + decrypted := privKey.DecryptToString(encrypted) + + fmt.Println(decrypted) + + if text != decrypted { + t.Errorf("Decrypted text is not the same as original text") + t.Fail() + } + +} + +func TestEnvCrypt(t *testing.T) { + fmt.Println("\n Test ENVCrypt (browser) encryption: ") + expected := "My secret text" + encrypted := "uJiPItNmQwRK7/zd6es7zmnyzR93ACp6tNaxGqUjabaCoD1X+KZFX7NE3GyGFVB4vdnwEXx+jsLyS3vXJN4ehR4r3f2J07q+SlEMVB5GxLH9gohulYzqwoQ5XW7zyZqqoD14QdX0fV1q6gZPhRiiMKmdE66+SC+ont/uDeXzr3wRnQMZf1dHQ9aAEjTqaTnGugPAbsDxRxxXBa/L44ZhNleV1wmKqTaEA0gFUtaikUjzS5yWj+9JQQi+EgzIVLGg+hVZ1ek3OASVbvlb4l7RyppJL6qxAjLyRhYOCT+I7R6v9uOAgiHGAhQF5c//FyrjiIHNiqVoFeG0FA9BiYaidw==" + privKey := crypto.RSAKey("UiZouRYYqk2Q8yOmmbyYLmn9J8YJ6C1A4JtN8wis0X8UlduvB7Xd4vCzrB7LXp9mq/zpo+CBZ41DzTJDtv/PNPWpmZInBwFeBFUfl+FPpWDldWptpuAAcqXD885MOs+0TqPhKtqWUUlkygftv+oZwv+YkxuMLvGZdipEEiwc2PIwV+F+beqo6MEd7Xwljhftp5YHkUy2x9SGgVlVWkVCUfkhqcgxXP9mLXz5h1rCoXDDhWr+gjKGfPQhmR7Lguon/fNIqNGN+ymG8ikO1A8if6ud4N8aOMQBNhirSUbb6KRZt4Dt8VgCOwcRpw4/NkEG7eUXiPmBQhsVjxkQ1dJG+QoKyNDMuALESVEjNAIAs2SQ3PBa65PFsqXQMq+kxnJQueSFCoZ0Lrh+4/iAJq01RqrlPSBsewHnIsN5bOTLg3FJluV/izyGdNdjwv6ue6UXkp2UisxovgjRBtbBZRDJcw+YGDecEhBSALkSuqBNE05VkQ6uiZQX23b2FhLtf7SrsgEPFwAOhwc/xRvTjvkLLiFSdd5i+5shQmE6KBfVngDl08Ctkobc7V/eo81tK+jsddh09JVjTOqN2S/nKjI7oASekNH1vfxRgEhd7CNJ33S2fd1eYGgsfLRzN9N6pW9e3tuXTpOSUJexXOWYaVJMUPMP20Da8/8awes4TIcJ980f9Q==") + decrypted := privKey.DecryptToString(encrypted) + fmt.Println(decrypted) + if decrypted != expected { + t.Errorf("Decrypted text is not the same as original text") + t.Fail() + } +} diff --git a/cmd/keypair/main.go b/cmd/keypair/main.go new file mode 100644 index 0000000..a7b4080 --- /dev/null +++ b/cmd/keypair/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "custodial/pkg/crypto" +) + +var KEY_DIR string = os.Getenv("KEYPAIR_DIR") + +func main() { + var command string = "" + if len(os.Args) > 1 { + command = os.Args[1] + } + + switch command { + case "generate": + if len(os.Args) < 3 { + fmt.Println("Usage: keypair generate ") + os.Exit(1) + } + kp, err := crypto.KeyPair{}.Random() + if err != nil { + panic(err) + } + kp.Save(os.Args[2]) + case "encrypt": + EnvWarn() + kp := crypto.KeyPair{}.Load(KEY_DIR) + pub := kp.PublicKeyBase64() + NoPipe(func() { + fmt.Println("Enter a string to encrypt:") + fmt.Println("") + }) + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + NoPipe(func() { + fmt.Println("Result:") + fmt.Println("") + }) + fmt.Println(pub.EncryptToString(text)) + case "decrypt": + EnvWarn() + kp := crypto.KeyPair{}.Load(KEY_DIR) + priv := kp.PrivateKeyBase64() + NoPipe(func() { + fmt.Println("Enter a string to decrypt:") + fmt.Println("") + }) + reader := bufio.NewReader(os.Stdin) + text, _ := reader.ReadString('\n') + NoPipe(func() { + fmt.Println("Result:") + fmt.Println("") + }) + fmt.Println(priv.DecryptToString(text)) + case "": + fmt.Println("Usage: keypair [command]") + fmt.Println("") + fmt.Println("Commands:") + fmt.Println(" generate - Generate a new keypair and save it to ") + fmt.Println(" encrypt - Encrypt a string using the public key") + fmt.Println(" decrypt - Decrypt a string using the private key") + fmt.Println("") + fmt.Println("Flags:") + fmt.Println(" --pipe - Pipe the result to stdout") + fmt.Println("") + fmt.Println("Environment Variables:") + fmt.Println(" KEYPAIR_DIR - The directory where the keypair is stored, defaults to the current working directory") + } +} + +func NoPipe(cb func()) { + var isPipe bool = false + if os.Args[len(os.Args)-1] == "--pipe" { + isPipe = true + } + if isPipe { + return + } else { + cb() + } +} + +func EnvWarn() { + if KEY_DIR == "" { + NoPipe(func() { + fmt.Println("Warning: KEYPAIR_DIR environment variable not set") + fmt.Println("Using current working directory") + }) + KEY_DIR, _ = os.Getwd() + } +} diff --git a/cmd/prebuild/main.go b/cmd/prebuild/main.go new file mode 100644 index 0000000..9509adb --- /dev/null +++ b/cmd/prebuild/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "os" + + "custodial/pkg/crypto" +) + +func main() { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + + var dir string = cwd + "/keys" + fmt.Println("Checking for directory: " + dir) + _, err = os.Stat(dir) + if os.IsNotExist(err) { + fmt.Println("Creating directory: " + dir) + err = os.Mkdir(dir, 0755) + if err != nil { + panic(err) + } + + fmt.Println("Generating a new keypair...") + kp, err := crypto.KeyPair{}.Random() + if err != nil { + panic(err) + } + kp.Save(dir) + fmt.Println("Keypair saved to: " + dir) + } else { + fmt.Println("Directory already exists: " + dir) + } + + fmt.Println("Checking if locker/keys.go exists...") + + keys := cwd + "/pkg/locker/keys.go" + _, err = os.Stat(keys) + if os.IsNotExist(err) { + fmt.Println("Creating locker/keys.go...") + f, err := os.Create(keys) + if err != nil { + panic(err) + } + defer f.Close() + + kp := crypto.KeyPair{}.Load(dir) + + fmt.Println("Writing to locker/keys.go...") + f.WriteString("package locker\n\nconst PRIVATE_KEY string = \"" + string(kp.PrivateKeyBase64()) + "\"") + fmt.Println("Done!") + } else { + fmt.Println("locker/keys.go already exists") + } + +} diff --git a/dev.env b/dev.env new file mode 100644 index 0000000..24749d6 --- /dev/null +++ b/dev.env @@ -0,0 +1,16 @@ +PUBLIC_KEY= +PRIVATE_KEY= +TRON_GRPC_NODE=grpc.nile.trongrid.io:50051 +TRC20_USDT_CONTRACT_ADDRESS=TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj +ERC20_USDT_CONTRACT_ADDRESS=0x7169D38820dfd117C3FA1f22a697dBA58d90BA06 +ERC20_USDT_CONTRACT_DECIMALS=6 +ETH_RPC_NODE=https://sepolia.infura.io/v3/a26b6e84efea4e518152b869791453ab +#DB_TYPE=postgres +#DB_CONNECTION_SETTINGS=host=localhost port=5435 user=user password=user dbname=custodial sslmode=disable + +DB_TYPE=sqlite +DB_CONNECTION_SETTINGS=./dev.sqlite + +PRIVATE__BIP39_MNEMONIC=NoFQyc7c7lfG8d80Vfc0aUqrQHjEsg8AJP5udi9WhrM99H1cQNJCxLcAxhahOeGthtS6cZUw/DNUsV9Cku7uLSf6TLlzXcRX0Vu/TMl2BfbdOrC72eNfDKg+/4BUVm1JggWvYBK40LmU+xseWeyo9cEYzD6GICy68WKmdYESEcWuaElEQxM4fj0MD8U5HjwvmaEyxeLOmKVVwYeUmVtnsMRFzB5rSX25ixJKMlocq1M3wAFpFKW7E+hKNsjGQC3xnP1eLtgI/PwTyCpTobZSMxxVC0CxoUemtRJxzU83SbnQujVWPQHGLopEBReHUNWkbCCP+8vDyuUs6sC6yZtdMA== +PRIVATE__API_MASTER_KEY=gBfBvPFn+4OxW/8GxQonKxCafp+vD00obOP13Jg/WqMd5ffVj5r/uUT9S1Edxyt48gGlrTNqdlqRAe3Cw2uOvGQXocbcwv1J/rGXhzJWXQrYX0l32BDphny5w28vYXubgXnhNW159ouOUpCmR8Ky6bzgnOCrXdDGD4W8UQURgRnimzB6HlAsN2gfSLkjWJRcPfH1GL5/9rd4jkS0ZAEG7XK/ejPXQrtWiRztUtWEKKvHzTuJoadveBAEaO9c6AAVq7bH1gF+df/wXv5XI3LsfgCAs6rCXiwpefOYeQejnkuk7xN3AJzapcgOVdZvHHCDGfIjAlxRYb5xw4tSXc/yig== +PRIVATE__PASSPHRASE= diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..49d4ac3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,44 @@ +version: "3.7" + +services: + custodial: + build: . + command: build/custodial --env=dev + ports: + - "28080:8080" + volumes: + - custodial-db-data:/mnt/data:rw + depends_on: + - postgres + env_file: + - dev.env + environment: + - DB_CONNECTION_SETTINGS=host=postgres port=5432 user=user password=user dbname=custodial sslmode=disable + + agent: + build: . + command: build/agent --env=dev + volumes: + - custodial-db-data:/mnt/data:rw + depends_on: + - postgres + env_file: + - dev.env + environment: + - WEBHOOK_URL=http://custodial:8080/utils/echo + - DB_CONNECTION_SETTINGS=host=postgres port=5432 user=user password=user dbname=custodial sslmode=disable + + postgres: + image: postgres:14.4-alpine + ports: + - "5435:5432" + volumes: + - custodial-postgres-data:/var/lib/postgresql/data:rw + environment: + - POSTGRES_PASSWORD=user + - POSTGRES_USER=user + - POSTGRES_DB=custodial + +volumes: + custodial-db-data: + custodial-postgres-data: diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..1e45bf9 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,2069 @@ +// Package docs 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}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/account/balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance for all currencies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/count": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Count accounts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountCountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/create": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new user account identified by account_id and index (incremental number)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Create a new user account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/hd/balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance for all currencies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountHDBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountHDBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List accounts paginating by (limit, offset)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "List all accounts", + "parameters": [ + { + "description": "Paging Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/rest.CreateAccountResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/spender-status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Check spender accounts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.SpenderBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/account-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get account balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthAccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/eth.EthAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/address-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get eth address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthAddressBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/eth.EthAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender contract address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT spender contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveContractRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-custody": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT Castody for Spender Account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveCustodyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveCustodyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveUSDTRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/create-account": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new etherum account identified by account_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Create a new account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateEthAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateEthAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/recharge-eth-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add ETH funds to the account from the spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Add ETH from spender account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.RechargeETHRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.RechargeETHResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/tx-info": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get transaction info", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get transaction info", + "parameters": [ + { + "description": "Tx Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthTransactionInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.EthTransactionInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-eth": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw ETH funds from ethereum account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw ETH", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawEthRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawEthRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt-by-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account using approved spender contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT by commission contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WidtdrawUSDTByContract" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account using approved spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT by Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawUSDTBySpenderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawSpenderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export private keys for the given index (uint32)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "summary": "Export private keys for the given index", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ExportV1Request" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ExportV1Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export-batch": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export private keys for the given range (uint32)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "summary": "Export private keys for the given range", + "parameters": [ + { + "description": "Paging Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ExportBatchV1Request" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/rest.ExportV1Response" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export-spender-keys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export spender private keys", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ExportV1Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/account-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronAccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/address-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronAddressBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender Contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveContractRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveUSDTResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-custody": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveCustodyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveCustodyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveUSDTRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveUSDTResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/create-account": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new tron account identified by account_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Create a new account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateTronAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateTronAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/recharge-trx-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add TRX funds from the spender account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Add TRX from spender account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.RechargeTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.RechargeTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/tx-info": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get transaction info", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get transaction info", + "parameters": [ + { + "description": "Tx Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronTransactionInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TronTransactionInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-trx": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw TRON funds from tron account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw TRX", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt-by-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account using approved comission contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT by commision contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WidtdrawUSDTByContract" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account using approved spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT by spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawUSDTBySpenderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawSpenderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "eth.EthAccountBalance": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "eth": { + "type": "string" + }, + "ethRaw": { + "type": "integer" + }, + "txFee": { + "type": "string" + }, + "usdt": { + "type": "string" + }, + "usdtRaw": { + "type": "integer" + } + } + }, + "rest.AccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.AccountBalanceResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "eth_allowance": { + "type": "string" + }, + "eth_hd_index": { + "type": "integer" + }, + "eth_spender": { + "type": "string" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "label": { + "type": "string" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + }, + "tron_allowance": { + "type": "string" + }, + "tron_hd_index": { + "type": "integer" + }, + "tron_spender": { + "type": "string" + } + } + }, + "rest.AccountCountResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + } + } + }, + "rest.AccountHDBalanceRequest": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } + }, + "rest.AccountHDBalanceResponse": { + "type": "object", + "properties": { + "eth_allowance": { + "type": "string" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + }, + "tron_allowance": { + "type": "string" + } + } + }, + "rest.AccountListRequest": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } + } + }, + "rest.CreateAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "label": { + "type": "string" + } + } + }, + "rest.CreateAccountResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "eth_address": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "tron_address": { + "type": "string" + } + } + }, + "rest.CreateEthAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.CreateEthAccountResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.CreateTronAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.CreateTronAccountResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.ETHApproveContractRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "contract_address": { + "type": "string" + } + } + }, + "rest.ETHApproveCustodyRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.ETHApproveCustodyResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.ETHApproveResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.ETHApproveUSDTRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "rest.EthAccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.EthAddressBalanceRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.EthTransactionInfoRequest": { + "type": "object", + "properties": { + "tx_id": { + "type": "string" + } + } + }, + "rest.EthTransactionInfoResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "rest.ExportBatchV1Request": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } + } + }, + "rest.ExportV1Request": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } + }, + "rest.ExportV1Response": { + "type": "object", + "properties": { + "eth_address": { + "type": "string" + }, + "eth_private_key": { + "type": "string" + }, + "tron_address": { + "type": "string" + }, + "tron_private_key": { + "type": "string" + } + } + }, + "rest.RechargeETHRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "rest.RechargeETHResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.RechargeTronRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "rest.RechargeTronResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.SpenderBalanceResponse": { + "type": "object", + "properties": { + "accounts_count": { + "type": "integer" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + } + }, + "rest.TRXApproveCustodyResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.TRXApproveUSDTResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.TRXAprroveContractRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "contract_address": { + "type": "string" + } + } + }, + "rest.TRXAprroveCustodyRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.TRXAprroveUSDTRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.TronAccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.TronAddressBalanceRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.TronTransactionInfoRequest": { + "type": "object", + "properties": { + "tx_id": { + "type": "string" + } + } + }, + "rest.TronTransactionInfoResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "rest.WidtdrawUSDTByContract": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "comission_amount": { + "type": "string" + }, + "contract_address": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawEthRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawEthResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.WithdrawSpenderResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid_commission": { + "type": "string" + }, + "txid_withdrawal": { + "type": "string" + } + } + }, + "rest.WithdrawTronRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawTronResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.WithdrawUSDTBySpenderRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "comission_amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "tron.TronAccountBalance": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "trx": { + "type": "string" + }, + "trxRaw": { + "type": "integer" + }, + "txFee": { + "type": "string" + }, + "usdt": { + "type": "string" + }, + "usdtRaw": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..6e56646 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,2040 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/account/balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance for all currencies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/count": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Count accounts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountCountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/create": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new user account identified by account_id and index (incremental number)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Create a new user account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/hd/balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance for all currencies", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountHDBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.AccountHDBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List accounts paginating by (limit, offset)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "List all accounts", + "parameters": [ + { + "description": "Paging Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.AccountListRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/rest.CreateAccountResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/account/spender-status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Check spender accounts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.SpenderBalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/account-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get account balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get account balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthAccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/eth.EthAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/address-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get eth address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthAddressBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/eth.EthAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender contract address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT spender contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveContractRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-custody": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT Castody for Spender Account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveCustodyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveCustodyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/approve-usdt-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender address for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Approve USDT spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ETHApproveUSDTRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ETHApproveResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/create-account": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new etherum account identified by account_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Create a new account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateEthAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateEthAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/recharge-eth-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add ETH funds to the account from the spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Add ETH from spender account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.RechargeETHRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.RechargeETHResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/tx-info": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get transaction info", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Get transaction info", + "parameters": [ + { + "description": "Tx Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.EthTransactionInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.EthTransactionInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-eth": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw ETH funds from ethereum account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw ETH", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawEthRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawEthRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt-by-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account using approved spender contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT by commission contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WidtdrawUSDTByContract" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawEthResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/eth/withdraw-usdt-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from etherum account using approved spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Ethereum" + ], + "summary": "Withdraw USDT by Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawUSDTBySpenderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawSpenderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export private keys for the given index (uint32)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "summary": "Export private keys for the given index", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ExportV1Request" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ExportV1Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export-batch": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export private keys for the given range (uint32)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "summary": "Export private keys for the given range", + "parameters": [ + { + "description": "Paging Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.ExportBatchV1Request" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/rest.ExportV1Response" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/export-spender-keys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Export spender private keys", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utils" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.ExportV1Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/account-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronAccountBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/address-balance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get address balance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get address balance", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronAddressBalanceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender Contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveContractRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveUSDTResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-custody": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveCustodyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveCustodyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/approve-usdt-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Approve USDT spender for account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Approve USDT Spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TRXAprroveUSDTRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TRXApproveUSDTResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/create-account": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new tron account identified by account_id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Create a new account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.CreateTronAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.CreateTronAccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/recharge-trx-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add TRX funds from the spender account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Add TRX from spender account", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.RechargeTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.RechargeTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/tx-info": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get transaction info", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Get transaction info", + "parameters": [ + { + "description": "Tx Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.TronTransactionInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.TronTransactionInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-trx": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw TRON funds from tron account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw TRX", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawTronRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt-by-contract": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account using approved comission contract", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT by commision contract", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WidtdrawUSDTByContract" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawTronResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, + "/tron/withdraw-usdt-by-spender": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Withdraw usdt funds from tron account using approved spender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tron" + ], + "summary": "Withdraw USDT by spender", + "parameters": [ + { + "description": "Account Info", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/rest.WithdrawUSDTBySpenderRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/rest.WithdrawSpenderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "eth.EthAccountBalance": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "eth": { + "type": "string" + }, + "ethRaw": { + "type": "integer" + }, + "txFee": { + "type": "string" + }, + "usdt": { + "type": "string" + }, + "usdtRaw": { + "type": "integer" + } + } + }, + "rest.AccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.AccountBalanceResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "eth_allowance": { + "type": "string" + }, + "eth_hd_index": { + "type": "integer" + }, + "eth_spender": { + "type": "string" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "label": { + "type": "string" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + }, + "tron_allowance": { + "type": "string" + }, + "tron_hd_index": { + "type": "integer" + }, + "tron_spender": { + "type": "string" + } + } + }, + "rest.AccountCountResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + } + } + }, + "rest.AccountHDBalanceRequest": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } + }, + "rest.AccountHDBalanceResponse": { + "type": "object", + "properties": { + "eth_allowance": { + "type": "string" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + }, + "tron_allowance": { + "type": "string" + } + } + }, + "rest.AccountListRequest": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } + } + }, + "rest.CreateAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "label": { + "type": "string" + } + } + }, + "rest.CreateAccountResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "eth_address": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "tron_address": { + "type": "string" + } + } + }, + "rest.CreateEthAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.CreateEthAccountResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.CreateTronAccountRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.CreateTronAccountResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.ETHApproveContractRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "contract_address": { + "type": "string" + } + } + }, + "rest.ETHApproveCustodyRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.ETHApproveCustodyResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.ETHApproveResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.ETHApproveUSDTRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "rest.EthAccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.EthAddressBalanceRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.EthTransactionInfoRequest": { + "type": "object", + "properties": { + "tx_id": { + "type": "string" + } + } + }, + "rest.EthTransactionInfoResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "rest.ExportBatchV1Request": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } + } + }, + "rest.ExportV1Request": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } + }, + "rest.ExportV1Response": { + "type": "object", + "properties": { + "eth_address": { + "type": "string" + }, + "eth_private_key": { + "type": "string" + }, + "tron_address": { + "type": "string" + }, + "tron_private_key": { + "type": "string" + } + } + }, + "rest.RechargeETHRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "rest.RechargeETHResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.RechargeTronRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "rest.RechargeTronResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.SpenderBalanceResponse": { + "type": "object", + "properties": { + "accounts_count": { + "type": "integer" + }, + "ethereum": { + "$ref": "#/definitions/eth.EthAccountBalance" + }, + "tron": { + "$ref": "#/definitions/tron.TronAccountBalance" + } + } + }, + "rest.TRXApproveCustodyResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.TRXApproveUSDTResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.TRXAprroveContractRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "contract_address": { + "type": "string" + } + } + }, + "rest.TRXAprroveCustodyRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.TRXAprroveUSDTRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.TronAccountBalanceRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "rest.TronAddressBalanceRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "rest.TronTransactionInfoRequest": { + "type": "object", + "properties": { + "tx_id": { + "type": "string" + } + } + }, + "rest.TronTransactionInfoResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "rest.WidtdrawUSDTByContract": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "comission_amount": { + "type": "string" + }, + "contract_address": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawEthRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawEthResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.WithdrawSpenderResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid_commission": { + "type": "string" + }, + "txid_withdrawal": { + "type": "string" + } + } + }, + "rest.WithdrawTronRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "rest.WithdrawTronResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "txid": { + "type": "string" + } + } + }, + "rest.WithdrawUSDTBySpenderRequest": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "comission_amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "tron.TronAccountBalance": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "trx": { + "type": "string" + }, + "trxRaw": { + "type": "integer" + }, + "txFee": { + "type": "string" + }, + "usdt": { + "type": "string" + }, + "usdtRaw": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..5874d0c --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1279 @@ +definitions: + eth.EthAccountBalance: + properties: + address: + type: string + eth: + type: string + ethRaw: + type: integer + txFee: + type: string + usdt: + type: string + usdtRaw: + type: integer + type: object + rest.AccountBalanceRequest: + properties: + account_id: + type: string + type: object + rest.AccountBalanceResponse: + properties: + account_id: + type: string + eth_allowance: + type: string + eth_hd_index: + type: integer + eth_spender: + type: string + ethereum: + $ref: '#/definitions/eth.EthAccountBalance' + label: + type: string + tron: + $ref: '#/definitions/tron.TronAccountBalance' + tron_allowance: + type: string + tron_hd_index: + type: integer + tron_spender: + type: string + type: object + rest.AccountCountResponse: + properties: + count: + type: integer + type: object + rest.AccountHDBalanceRequest: + properties: + index: + type: integer + type: object + rest.AccountHDBalanceResponse: + properties: + eth_allowance: + type: string + ethereum: + $ref: '#/definitions/eth.EthAccountBalance' + tron: + $ref: '#/definitions/tron.TronAccountBalance' + tron_allowance: + type: string + type: object + rest.AccountListRequest: + properties: + limit: + type: integer + offset: + type: integer + type: object + rest.CreateAccountRequest: + properties: + account_id: + type: string + index: + type: integer + label: + type: string + type: object + rest.CreateAccountResponse: + properties: + account_id: + type: string + eth_address: + type: string + index: + type: integer + label: + type: string + tron_address: + type: string + type: object + rest.CreateEthAccountRequest: + properties: + account_id: + type: string + type: object + rest.CreateEthAccountResponse: + properties: + address: + type: string + type: object + rest.CreateTronAccountRequest: + properties: + account_id: + type: string + type: object + rest.CreateTronAccountResponse: + properties: + address: + type: string + type: object + rest.ETHApproveContractRequest: + properties: + account_id: + type: string + contract_address: + type: string + type: object + rest.ETHApproveCustodyRequest: + properties: + address: + type: string + type: object + rest.ETHApproveCustodyResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.ETHApproveResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.ETHApproveUSDTRequest: + properties: + account_id: + type: string + type: object + rest.ErrorResponse: + properties: + error: + type: string + type: object + rest.EthAccountBalanceRequest: + properties: + account_id: + type: string + type: object + rest.EthAddressBalanceRequest: + properties: + address: + type: string + type: object + rest.EthTransactionInfoRequest: + properties: + tx_id: + type: string + type: object + rest.EthTransactionInfoResponse: + properties: + id: + type: string + status: + type: string + type: object + rest.ExportBatchV1Request: + properties: + limit: + type: integer + offset: + type: integer + type: object + rest.ExportV1Request: + properties: + index: + type: integer + type: object + rest.ExportV1Response: + properties: + eth_address: + type: string + eth_private_key: + type: string + tron_address: + type: string + tron_private_key: + type: string + type: object + rest.RechargeETHRequest: + properties: + account_id: + type: string + amount: + type: string + type: object + rest.RechargeETHResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.RechargeTronRequest: + properties: + account_id: + type: string + amount: + type: string + type: object + rest.RechargeTronResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.SpenderBalanceResponse: + properties: + accounts_count: + type: integer + ethereum: + $ref: '#/definitions/eth.EthAccountBalance' + tron: + $ref: '#/definitions/tron.TronAccountBalance' + type: object + rest.TRXApproveCustodyResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.TRXApproveUSDTResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.TRXAprroveContractRequest: + properties: + account_id: + type: string + contract_address: + type: string + type: object + rest.TRXAprroveCustodyRequest: + properties: + address: + type: string + type: object + rest.TRXAprroveUSDTRequest: + properties: + account_id: + type: string + type: object + rest.TronAccountBalanceRequest: + properties: + account_id: + type: string + type: object + rest.TronAddressBalanceRequest: + properties: + address: + type: string + type: object + rest.TronTransactionInfoRequest: + properties: + tx_id: + type: string + type: object + rest.TronTransactionInfoResponse: + properties: + id: + type: string + status: + type: string + type: object + rest.WidtdrawUSDTByContract: + properties: + account_id: + type: string + amount: + type: string + comission_amount: + type: string + contract_address: + type: string + to: + type: string + type: object + rest.WithdrawEthRequest: + properties: + account_id: + type: string + amount: + type: string + to: + type: string + type: object + rest.WithdrawEthResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.WithdrawSpenderResponse: + properties: + status: + type: string + txid_commission: + type: string + txid_withdrawal: + type: string + type: object + rest.WithdrawTronRequest: + properties: + account_id: + type: string + amount: + type: string + to: + type: string + type: object + rest.WithdrawTronResponse: + properties: + status: + type: string + txid: + type: string + type: object + rest.WithdrawUSDTBySpenderRequest: + properties: + account_id: + type: string + amount: + type: string + comission_amount: + type: string + to: + type: string + type: object + tron.TronAccountBalance: + properties: + address: + type: string + trx: + type: string + trxRaw: + type: integer + txFee: + type: string + usdt: + type: string + usdtRaw: + type: integer + type: object +info: + contact: {} +paths: + /account/balance: + post: + consumes: + - application/json + description: Get account balance for all currencies + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.AccountBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.AccountBalanceResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + tags: + - Account + /account/count: + get: + consumes: + - application/json + description: Count accounts + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.AccountCountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + tags: + - Account + /account/create: + post: + consumes: + - application/json + description: Create a new user account identified by account_id and index (incremental + number) + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.CreateAccountRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.CreateAccountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Create a new user account + tags: + - Account + /account/hd/balance: + post: + consumes: + - application/json + description: Get account balance for all currencies + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.AccountHDBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.AccountHDBalanceResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + tags: + - Account + /account/list: + post: + consumes: + - application/json + description: List accounts paginating by (limit, offset) + parameters: + - description: Paging Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.AccountListRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/rest.CreateAccountResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: List all accounts + tags: + - Account + /account/spender-status: + get: + consumes: + - application/json + description: Check spender accounts + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.SpenderBalanceResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + tags: + - Account + /eth/account-balance: + post: + consumes: + - application/json + description: Get account balance + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.EthAccountBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/eth.EthAccountBalance' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get account balance + tags: + - Ethereum + /eth/address-balance: + post: + consumes: + - application/json + description: Get address balance + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.EthAddressBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/eth.EthAccountBalance' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get eth address balance + tags: + - Ethereum + /eth/approve-usdt-contract: + post: + consumes: + - application/json + description: Approve USDT spender contract address for account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.ETHApproveContractRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.ETHApproveResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT spender contract + tags: + - Ethereum + /eth/approve-usdt-custody: + post: + consumes: + - application/json + description: Approve USDT spender address for account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.ETHApproveCustodyRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.ETHApproveCustodyResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT Castody for Spender Account + tags: + - Ethereum + /eth/approve-usdt-spender: + post: + consumes: + - application/json + description: Approve USDT spender address for account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.ETHApproveUSDTRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.ETHApproveResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT spender + tags: + - Ethereum + /eth/create-account: + post: + consumes: + - application/json + description: Create a new etherum account identified by account_id + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.CreateEthAccountRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.CreateEthAccountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Create a new account + tags: + - Ethereum + /eth/recharge-eth-by-spender: + post: + consumes: + - application/json + description: Add ETH funds to the account from the spender + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.RechargeETHRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.RechargeETHResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Add ETH from spender account + tags: + - Ethereum + /eth/tx-info: + post: + consumes: + - application/json + description: Get transaction info + parameters: + - description: Tx Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.EthTransactionInfoRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.EthTransactionInfoResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get transaction info + tags: + - Ethereum + /eth/withdraw-eth: + post: + consumes: + - application/json + description: Withdraw ETH funds from ethereum account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawEthRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawEthResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw ETH + tags: + - Ethereum + /eth/withdraw-usdt: + post: + consumes: + - application/json + description: Withdraw usdt funds from etherum account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawEthRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawEthResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT + tags: + - Ethereum + /eth/withdraw-usdt-by-contract: + post: + consumes: + - application/json + description: Withdraw usdt funds from etherum account using approved spender + contract + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WidtdrawUSDTByContract' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawEthResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT by commission contract + tags: + - Ethereum + /eth/withdraw-usdt-by-spender: + post: + consumes: + - application/json + description: Withdraw usdt funds from etherum account using approved spender + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawUSDTBySpenderRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawSpenderResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT by Spender + tags: + - Ethereum + /export: + post: + consumes: + - application/json + description: Export private keys for the given index (uint32) + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.ExportV1Request' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.ExportV1Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Export private keys for the given index + tags: + - Utils + /export-batch: + post: + consumes: + - application/json + description: Export private keys for the given range (uint32) + parameters: + - description: Paging Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.ExportBatchV1Request' + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/rest.ExportV1Response' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Export private keys for the given range + tags: + - Utils + /export-spender-keys: + get: + consumes: + - application/json + description: Export spender private keys + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.ExportV1Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + tags: + - Utils + /tron/account-balance: + post: + consumes: + - application/json + description: Get address balance + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TronAccountBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/tron.TronAccountBalance' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get address balance + tags: + - Tron + /tron/address-balance: + post: + consumes: + - application/json + description: Get address balance + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TronAddressBalanceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/tron.TronAccountBalance' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get address balance + tags: + - Tron + /tron/approve-usdt-contract: + post: + consumes: + - application/json + description: Approve USDT spender for contract + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TRXAprroveContractRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.TRXApproveUSDTResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT Spender Contract + tags: + - Tron + /tron/approve-usdt-custody: + post: + consumes: + - application/json + description: Approve USDT spender for account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TRXAprroveCustodyRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.TRXApproveCustodyResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT Spender + tags: + - Tron + /tron/approve-usdt-spender: + post: + consumes: + - application/json + description: Approve USDT spender for account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TRXAprroveUSDTRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.TRXApproveUSDTResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Approve USDT Spender + tags: + - Tron + /tron/create-account: + post: + consumes: + - application/json + description: Create a new tron account identified by account_id + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.CreateTronAccountRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.CreateTronAccountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Create a new account + tags: + - Tron + /tron/recharge-trx-by-spender: + post: + consumes: + - application/json + description: Add TRX funds from the spender account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.RechargeTronRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.RechargeTronResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Add TRX from spender account + tags: + - Tron + /tron/tx-info: + post: + consumes: + - application/json + description: Get transaction info + parameters: + - description: Tx Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.TronTransactionInfoRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.TronTransactionInfoResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Get transaction info + tags: + - Tron + /tron/withdraw-trx: + post: + consumes: + - application/json + description: Withdraw TRON funds from tron account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawTronRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawTronResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw TRX + tags: + - Tron + /tron/withdraw-usdt: + post: + consumes: + - application/json + description: Withdraw usdt funds from tron account + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawTronRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawTronResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT + tags: + - Tron + /tron/withdraw-usdt-by-contract: + post: + consumes: + - application/json + description: Withdraw usdt funds from tron account using approved comission + contract + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WidtdrawUSDTByContract' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawTronResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT by commision contract + tags: + - Tron + /tron/withdraw-usdt-by-spender: + post: + consumes: + - application/json + description: Withdraw usdt funds from tron account using approved spender + parameters: + - description: Account Info + in: body + name: message + required: true + schema: + $ref: '#/definitions/rest.WithdrawUSDTBySpenderRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/rest.WithdrawSpenderResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/rest.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Withdraw USDT by spender + tags: + - Tron +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9e97b80 --- /dev/null +++ b/go.mod @@ -0,0 +1,104 @@ +module custodial + +go 1.20 + +require ( + github.com/ethereum/go-ethereum v1.13.14 + github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259 + github.com/gin-gonic/gin v1.9.1 + github.com/joho/godotenv v1.5.1 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.1 + google.golang.org/grpc v1.56.2 + gorm.io/driver/postgres v1.5.2 + gorm.io/driver/sqlite v1.5.2 + gorm.io/gorm v1.25.2 +) + +require github.com/btcsuite/btcd v0.20.1-beta + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/panjf2000/ants v1.3.0 // indirect + github.com/supranational/blst v0.3.11 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.5.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 + github.com/btcsuite/btcutil v1.0.2 + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-ole/go-ole v1.3.0 // 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-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pborman/uuid v1.2.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rjeczalik/notify v0.9.3 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/shengdoushi/base58 v1.0.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/tyler-smith/go-bip39 v1.1.0 + github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/atomic v1.6.0 // indirect + go.uber.org/multierr v1.5.0 // indirect + go.uber.org/zap v1.15.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/protobuf v1.30.0 + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b94c9c0 --- /dev/null +++ b/go.sum @@ -0,0 +1,373 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +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 v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +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/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= +github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= +github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259 h1:HAcHwvPamxsjiPkU6TtFsHbYYdauhJ1BnDt631nPZvI= +github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259/go.mod h1:Sj3nZuicr/3RoekvShKtFRwmYVDSOE/X1gLez8f+7ps= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +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/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +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/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +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.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +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/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 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/panjf2000/ants v1.3.0 h1:8pQ+8leaLc9lys2viEEr8md0U4RN6uOSUCE9bOYjQ9M= +github.com/panjf2000/ants v1.3.0/go.mod h1:AaACblRPzq35m1g3enqYcxspbbiOJJYaxU2wMpm1cXY= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= +github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +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/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-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/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc= +gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/keys/private.pem b/keys/private.pem new file mode 100644 index 0000000..025baa7 --- /dev/null +++ b/keys/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClcwfZIpKWqFc4 +Llrk3c8OLzveuSy5x/ZQCbmECSOjJFJ1jXlldbCo0XtYTjO9XMEFL6d357GjyXyC +4r6mU1HOx0O6Z2wOQJnVqt/HVEIIBDoZBG9rOITSuINxSrfuJKjS7GcFJSco5eSC +OKAIpOFDl/j80sM18eV8UPRkH5yM3KFKwqpR6Whv4RvgyqPHH5B2ZXo9EIlt2veH +HCyipVYdWU6EOFg3L/AaOhbynhhUu1CY06OGikyMqlls6a+KZ1KCp5CeaywLgU34 ++Y63GjD2dzW5yQpZm3daJ9U0xraLAjjeyqzwBlfTyLw6qBY9r+WPlSKe5bsRMtbz +yVv5EnZbAgMBAAECggEATUFbZs63+FV+9KLgmoHgT1VK9YMuGUn///uqfrbtxx4M +ywtWpkPAS/QVTnSlwERxdQR9hIXR8xMAavWJ5Ix/ZLizLXVhhX4w1w7FE4SKmMew +gUIK7NwlWWgDKIGlRTQlCOiOal6g3H4Mp6ndQGwNK8zo3NVlhekAKX57v8zrAvK6 +gWL/P3lXAwZVlSKxGREHZb7bX2mVWv4tSXFEKp1p2pzlV/qWCTcn67mSygotkk2E +xB9Knm9d0V2l+klzbQHdx38RhwFvfBetf84w8a5Gb+Fv4fwvi6Um9cbcGtW1IvX0 +cb24YG2MfJKVKEAI9hhfGDJZZ99hzI2nljY2qu374QKBgQDYsymJw/3871ra+Ot6 +iXVHs76jYhzqL5qSZ6TYeN2t1hT6GrQAoQ3SNCUPcNU9mGQYfWEQ06z92s7qSUYS +jLEIYCqhD6Uuk8uV4uctK58Bbx4rSvARPJ1Y3eD9tA0GEwhZS6++tsNS0WARlV52 +pIVyMrjRXDw1GvqYIPJ2InyY2QKBgQDDdG8Tt8I9tNj3WCJM5YJCi/THgsFBaUmT +9LsPEMEC4ExpQGjerHWHGHJMywH+azQKs9UYZdokpM++pSh1kbP9qbeRslGOMbKp +JlqEv2/xBUhYYyKksLzYvj+QmYWsUFTT4ds32vM9JAMpivW5ck4x1vNtGUv3dwkl +i1VGokMoUwKBgE1Zfk03kVSUl2isC1m88Qj8BuNI5StOfK0fo77FPdOMJAa2O2Qy +GL3ccRIW43bOC4SWVGxuMkSWst778rAyWgq0UOMWs45xoOzKhlwgQux/HlSztgdh +DIUpBeNpPnDZoFRHaN75W7UXGWSNXZ+Z0CxYIJJSiwclrydYM1OpsbHZAoGBALhH +qQqwMKU5Q29BW2Wg5kWT6z/IGilv+X1UOqGjrDbn/2Mk5Ts84rpy5CFfLgwQS0rj +7sBIF3qBIZWf5hujOk6pm3f05kvos4gjryiFzicyUdlz7o/UStkX1pqhBJVIUBJN +WgC5oKg+sfSTHcaw7OS0w2JTfXpecvNBAS/NgQAdAoGBAK+9iuKZKasJjH7Jn8Ak +2HTNbfDCJ+Q4w3j2prqeXPlAdNLVpd33CHTSsm2FIFWmkN13dYT3WDmNNps0LrR3 +7hNzTu1+VpP4V5szVyi9tFWBLJ9NWJ/dZ84GeePh9uG7wGsLQT8El15pErZ9zFNG +QAKPUxfVPEBc/NGMSr6ZRUL+ +-----END PRIVATE KEY----- diff --git a/keys/private.rsa b/keys/private.rsa new file mode 100644 index 0000000..80dc028 --- /dev/null +++ b/keys/private.rsa @@ -0,0 +1 @@ +TUFbZs63+FV+9KLgmoHgT1VK9YMuGUn///uqfrbtxx4MywtWpkPAS/QVTnSlwERxdQR9hIXR8xMAavWJ5Ix/ZLizLXVhhX4w1w7FE4SKmMewgUIK7NwlWWgDKIGlRTQlCOiOal6g3H4Mp6ndQGwNK8zo3NVlhekAKX57v8zrAvK6gWL/P3lXAwZVlSKxGREHZb7bX2mVWv4tSXFEKp1p2pzlV/qWCTcn67mSygotkk2ExB9Knm9d0V2l+klzbQHdx38RhwFvfBetf84w8a5Gb+Fv4fwvi6Um9cbcGtW1IvX0cb24YG2MfJKVKEAI9hhfGDJZZ99hzI2nljY2qu374QoKpXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww== \ No newline at end of file diff --git a/keys/public.pem b/keys/public.pem new file mode 100644 index 0000000..2f4b3bf --- /dev/null +++ b/keys/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApXMH2SKSlqhXOC5a5N3P +Di873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNR +zsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKTh +Q5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVW +HVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow +9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2 +WwIDAQAB +-----END PUBLIC KEY----- diff --git a/keys/public.rsa b/keys/public.rsa new file mode 100644 index 0000000..ecc639c --- /dev/null +++ b/keys/public.rsa @@ -0,0 +1 @@ +pXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww== \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..9195ef8 --- /dev/null +++ b/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "flag" + "fmt" + + "custodial/pkg/rest" + + "github.com/joho/godotenv" +) + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +func main() { + + env := flag.String("env", "dev", "Environment") + flag.Parse() + + if *env != "production" { + err := godotenv.Load("dev.env") + if err != nil { + fmt.Println("Error loading dev.env file") + } + } + + router := rest.Router() + router.Run(":8080") +} diff --git a/pkg/crypto/keypair.go b/pkg/crypto/keypair.go new file mode 100644 index 0000000..142e5aa --- /dev/null +++ b/pkg/crypto/keypair.go @@ -0,0 +1,93 @@ +package crypto + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "os" +) + +type KeyPair struct { + privateKeyBase64 RSAKey + publicKeyBase64 RSAKey + private *rsa.PrivateKey + public *rsa.PublicKey +} + +func (kp KeyPair) PrivateKeyBase64() RSAKey { + return kp.privateKeyBase64 +} + +func (kp KeyPair) PublicKeyBase64() RSAKey { + return kp.publicKeyBase64 +} + +func (kp KeyPair) Random() (*KeyPair, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + kp.privateKeyBase64, kp.publicKeyBase64 = KeysToBase64(privateKey) + kp.private = privateKey + kp.public = &privateKey.PublicKey + + return &kp, nil +} + +func (kp KeyPair) FromBase64(privateKeyBase64 string) (*KeyPair, error) { + privateKey, err := Base64ToKeys(privateKeyBase64) + if err != nil { + return nil, err + } + + kp.privateKeyBase64, kp.publicKeyBase64 = KeysToBase64(privateKey) + kp.private = privateKey + kp.public = &privateKey.PublicKey + + return &kp, nil +} + +func (kp *KeyPair) Save(dir string) { + priv, err := os.Create(dir + "/private.rsa") + throw(err) + priv.WriteString(string(kp.privateKeyBase64)) + defer priv.Close() + + pub, err := os.Create(dir + "/public.rsa") + throw(err) + pub.WriteString(string(kp.publicKeyBase64)) + defer pub.Close() + + data, err := x509.MarshalPKIXPublicKey(kp.public) + + throw(err) + pemkey := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: data, + } + pubPem, err := os.Create(dir + "/public.pem") + throw(err) + pem.Encode(pubPem, pemkey) + + defer pubPem.Close() + data, err = x509.MarshalPKCS8PrivateKey(kp.private) + throw(err) + pemkey = &pem.Block{ + Type: "PRIVATE KEY", + Bytes: data, + } + privPem, err := os.Create(dir + "/private.pem") + throw(err) + pem.Encode(privPem, pemkey) + + defer privPem.Close() +} + +func (kp KeyPair) Load(dir string) *KeyPair { + priv, err := os.ReadFile(dir + "/private.rsa") + throw(err) + data, _ := kp.FromBase64(string(priv)) + return data +} diff --git a/pkg/crypto/rsa.go b/pkg/crypto/rsa.go new file mode 100644 index 0000000..46771eb --- /dev/null +++ b/pkg/crypto/rsa.go @@ -0,0 +1,50 @@ +package crypto + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "encoding/base64" + "math/big" +) + +type RSAKey string + +func (key *RSAKey) Encrypt(data []byte) []byte { + pk := key.PubFromBase64() + encrypted, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, &pk, data, nil) + throw(err) + return encrypted +} + +func (key *RSAKey) Decrypt(data []byte) []byte { + pk := key.PKFromBase64() + decrypted, err := rsa.DecryptOAEP(sha512.New(), rand.Reader, &pk, data, nil) + throw(err) + return decrypted +} + +func (key *RSAKey) EncryptToString(str string) string { + return base64.StdEncoding.EncodeToString(key.Encrypt([]byte(str))) +} + +func (key *RSAKey) DecryptToString(b64 string) string { + raw, _ := base64.StdEncoding.DecodeString(b64) + return string(key.Decrypt(raw)) +} + +func (key *RSAKey) PubFromBase64() rsa.PublicKey { + raw, err := base64.StdEncoding.DecodeString(string(*key)) + throw(err) + pk := rsa.PublicKey{ + N: new(big.Int).SetBytes(raw), + E: 65537, + } + return pk +} + +func (key *RSAKey) PKFromBase64() rsa.PrivateKey { + pk, err := Base64ToKeys(string(*key)) + throw(err) + return *pk +} diff --git a/pkg/crypto/utils.go b/pkg/crypto/utils.go new file mode 100644 index 0000000..4903b7b --- /dev/null +++ b/pkg/crypto/utils.go @@ -0,0 +1,32 @@ +package crypto + +import ( + "crypto/rsa" + "encoding/base64" + "math/big" + "strings" +) + +func KeysToBase64(privateKey *rsa.PrivateKey) (RSAKey, RSAKey) { + pub := base64.StdEncoding.EncodeToString(privateKey.PublicKey.N.Bytes()) + privBytes := privateKey.D.Bytes()[:] + privBytes = append(privBytes, '\n', '\n') + privBytes = append(privBytes, privateKey.PublicKey.N.Bytes()[:]...) + priv := base64.StdEncoding.EncodeToString(privBytes) + return RSAKey(priv), RSAKey(pub) +} + +func Base64ToKeys(privateKeyBase64 string) (*rsa.PrivateKey, error) { + privateKeyStore, err := base64.StdEncoding.DecodeString(privateKeyBase64) + throw(err) + store := strings.Split(string(privateKeyStore), "\n\n") + privateKey := []byte(store[0]) + publicKey := []byte(store[1]) + return &rsa.PrivateKey{D: new(big.Int).SetBytes(privateKey), PublicKey: rsa.PublicKey{N: new(big.Int).SetBytes(publicKey), E: 65537}}, nil +} + +func throw(err error) { + if err != nil { + panic(err) + } +} diff --git a/pkg/eth/eth.go b/pkg/eth/eth.go new file mode 100644 index 0000000..35066ac --- /dev/null +++ b/pkg/eth/eth.go @@ -0,0 +1,183 @@ +package eth + +import ( + "fmt" + "os" + "strconv" + "sync" +) + +const CONTRACT_ADDRESS string = "ERC20_USDT_CONTRACT_ADDRESS" +const CONTRACT_DECIMALS string = "ERC20_USDT_CONTRACT_DECIMALS" +const ETH_RPC_NODE string = "ETH_RPC_NODE" + +type EthNode struct { + usdtContractAddress string + usdtContractDecimals int + rpcNode string +} + +var ethNode *EthNode +var once sync.Once + +func GetERC20ContractAddress() string { + return ethNode.usdtContractAddress +} + +func (e EthNode) Init() (*EthNode, error) { + + if ethNode != nil { + return ethNode, nil + } + + usdtContract, isAddressPresent := os.LookupEnv(CONTRACT_ADDRESS) + if !isAddressPresent { + return nil, fmt.Errorf("missing environment variable: %s", CONTRACT_ADDRESS) + } + + usdtContractDecimals, isDecimalsPresent := os.LookupEnv(CONTRACT_DECIMALS) + if !isDecimalsPresent { + return nil, fmt.Errorf("missing environment variable: %s", CONTRACT_DECIMALS) + } + + rpcNode, isRpcNodePresent := os.LookupEnv(ETH_RPC_NODE) + if !isRpcNodePresent { + return nil, fmt.Errorf("missing environment variable: %s", ETH_RPC_NODE) + } + + if ethNode == nil { + once.Do(func() { + e.usdtContractAddress = usdtContract + e.usdtContractDecimals, _ = strconv.Atoi(usdtContractDecimals) + e.rpcNode = rpcNode + ethNode = &e + }) + } + + return ethNode, nil +} + +const COMMON_ABI string = ` +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ { "name": "", "type": "string" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "approve", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ { "name": "", "type": "uint8" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ { "name": "_owner", "type": "address" } ], + "name": "balanceOf", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ { "name": "", "type": "string" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_token", "type": "address" }, + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" }, + { "name": "_camount", "type": "uint256" } + ], + "name": "chargeTransfer", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" } + ], + "name": "allowance", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { "inputs": [], "payable": false, "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "_from", "type": "address" }, + { "indexed": true, "name": "_to", "type": "address" }, + { "indexed": false, "name": "_value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "_owner", "type": "address" }, + { "indexed": true, "name": "_spender", "type": "address" }, + { "indexed": false, "name": "_value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + } +] +` diff --git a/pkg/eth/eth_account.go b/pkg/eth/eth_account.go new file mode 100644 index 0000000..134f544 --- /dev/null +++ b/pkg/eth/eth_account.go @@ -0,0 +1,116 @@ +package eth + +import ( + "crypto/ecdsa" + "strings" + + "custodial/pkg/locker" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil/hdkeychain" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tyler-smith/go-bip39" +) + +const MAX_ACCOUNT_INDEX int = 4294967295 + +type EthAccount struct { + privateKey *ecdsa.PrivateKey + publicKey *ecdsa.PublicKey + address common.Address +} + +func (e *EthAccount) PrivateKey() *ecdsa.PrivateKey { + return e.privateKey +} + +func (e *EthAccount) PublicKey() *ecdsa.PublicKey { + return e.publicKey +} + +func (e *EthAccount) Address() common.Address { + return e.address +} + +const path = "m/44'/60'/0'/0" + +var master *hdkeychain.ExtendedKey + +func InitMaster(mnemonic, passphrase string) { + if master != nil { + return + } + + seed := bip39.NewSeed(strings.TrimSpace(mnemonic), strings.TrimSpace(passphrase)) + dpath, err := accounts.ParseDerivationPath(path) + if err != nil { + panic(err) + } + + key, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + panic(err) + } + + for _, n := range dpath { + key, err = key.Child(n) + if err != nil { + panic(err) + } + } + + master = key + privateKey, err := key.ECPrivKey() + privateKeyECDSA := privateKey.ToECDSA() + if err != nil { + panic(err) + } + + publicKey := privateKeyECDSA.Public() + _, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + panic("error casting public key to ECDSA") + } + +} + +func InitMasterFromPrivateSettings() { + mnemonic := locker.GetPrivateEnv("BIP39_MNEMONIC") + passphrase := locker.GetPrivateEnv("PASSPHRASE") + if mnemonic == "" { + panic("BIP39_MNEMONIC is not set") + } + InitMaster(mnemonic, passphrase) +} + +func DeriveAccount(index int) (*EthAccount, error) { + + address, err := master.Child(uint32(index)) + if err != nil { + return nil, err + } + + privateKey, err := address.ECPrivKey() + + privateKeyECDSA := privateKey.ToECDSA() + if err != nil { + return nil, err + } + + publicKey, _ := address.ECPubKey() + publicKeyECDSA := publicKey.ToECDSA() + + e := EthAccount{ + privateKey: privateKeyECDSA, + publicKey: publicKeyECDSA, + address: crypto.PubkeyToAddress(*publicKeyECDSA), + } + + return &e, nil +} + +func GetSpender() (*EthAccount, error) { + return DeriveAccount(MAX_ACCOUNT_INDEX) +} diff --git a/pkg/eth/eth_approve.go b/pkg/eth/eth_approve.go new file mode 100644 index 0000000..bc5dd23 --- /dev/null +++ b/pkg/eth/eth_approve.go @@ -0,0 +1,64 @@ +package eth; + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +func (e *EthAccount) ApproveUSDTSpender(spender string, amount string) (string, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return "", err + } + + metodId := crypto.Keccak256Hash([]byte("approve(address,uint256)")).Bytes()[:4] + paddedAddress := common.LeftPadBytes(common.HexToAddress(spender).Bytes(), 32) + + amountToSend := floatStringToDec(amount, ethNode.usdtContractDecimals) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + + var data []byte + data = append(data, metodId...) + data = append(data, paddedAddress...) + data = append(data, paddedAmount...) + + nonce, err := client.PendingNonceAt(context.Background(), e.address) + if err != nil { + return "", err + } + + gasLimit := uint64(60000) + gasTipCap, err := client.SuggestGasTipCap(context.Background()) + if err != nil { + return "", err + } + chainId, _ := client.ChainID(context.Background()) + toAddress := common.HexToAddress(ethNode.usdtContractAddress) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainId, + To: &toAddress, + Nonce: nonce, + Value: big.NewInt(0), + Gas: gasLimit, + Data: data, + GasTipCap: gasTipCap, + GasFeeCap: big.NewInt(20000000000), + }) + + signedTx, err := e.SignTx(tx) + if err != nil { + return "", err + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + return "", err + } + + return signedTx.Hash().Hex(), nil +} \ No newline at end of file diff --git a/pkg/eth/eth_balance.go b/pkg/eth/eth_balance.go new file mode 100644 index 0000000..5a0829a --- /dev/null +++ b/pkg/eth/eth_balance.go @@ -0,0 +1,107 @@ +package eth + +import ( + "context" + + "math" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +type EthAccountBalance struct { + Address string `json:"address"` + USDT string `json:"usdt"` + ETH string `json:"eth"` + USDTRaw int64 `json:"usdtRaw"` + ETHRaw int64 `json:"ethRaw"` + TxFee string `json:"txFee"` +} + +type request struct { + To string `json:"to"` + Data string `json:"data"` +} + +func EthBalance(address string) (*EthAccountBalance, error) { + + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return nil, err + } + + account := common.HexToAddress(address) + balance, err := client.BalanceAt(context.Background(), account, nil) + if err != nil { + return nil, err + } + + fbalance := new(big.Float) + fbalance.SetString(balance.String()) + ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) + + rpcClient, err := rpc.DialHTTP(ethNode.rpcNode) + if err != nil { + return nil, err + } + + data := crypto.Keccak256Hash([]byte("balanceOf(address)")).String()[0:10] + "000000000000000000000000" + address[2:] + msg := request{ethNode.usdtContractAddress, data} + var res string + err = rpcClient.Call(&res, "eth_call", msg, "latest") + if err != nil { + return nil, err + } + + addr := common.HexToAddress(ethNode.usdtContractAddress) + estimation, _ := client.EstimateGas(context.Background(), ethereum.CallMsg{ + To: &addr, + Data: []byte(data + address[2:]), + }) + txFeeValue := new(big.Float).Quo(big.NewFloat(float64(estimation)*1.5), big.NewFloat(math.Pow10(8))) + + usdtRaw := new(big.Int) + if len(res) > 2 { + usdtRaw.SetString(string(res[2:]), 16) + } + + usdtValue := new(big.Float).Quo(big.NewFloat(float64(usdtRaw.Int64())), big.NewFloat(math.Pow10(ethNode.usdtContractDecimals))) + + var accountBalance EthAccountBalance = EthAccountBalance{ + Address: address, + USDT: usdtValue.String(), + ETH: ethValue.String(), + USDTRaw: usdtRaw.Int64(), + ETHRaw: balance.Int64(), + TxFee: txFeeValue.String(), + } + + return &accountBalance, nil +} + +func EthAllowance(address string, spender string) (int64, error) { + + rpcClient, err := rpc.DialHTTP(ethNode.rpcNode) + if err != nil { + return 0, err + } + + data := crypto.Keccak256Hash([]byte("allowance(address,address)")).String()[0:10] + "000000000000000000000000" + address[2:] + "000000000000000000000000" + spender[2:] + msg := request{ethNode.usdtContractAddress, data} + var res string + err = rpcClient.Call(&res, "eth_call", msg, "latest") + if err != nil { + return 0, err + } + + usdtRaw := new(big.Int) + if len(res) > 2 { + usdtRaw.SetString(string(res[2:]), 16) + } + + return usdtRaw.Int64(), nil +} diff --git a/pkg/eth/eth_block.go b/pkg/eth/eth_block.go new file mode 100644 index 0000000..ac4d728 --- /dev/null +++ b/pkg/eth/eth_block.go @@ -0,0 +1,37 @@ +package eth + +import ( + "context" + "fmt" + + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +func EthLastBlock() (uint64, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return 0, err + } + last_eth_block, err := client.BlockNumber(context.Background()) + if err != nil { + return 0, err + } + return last_eth_block, nil +} + +func EthBlockByNumber(blockNumber uint64) (*types.Block, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return nil, err + } + _blockNumber := big.NewInt(int64(blockNumber)) + fmt.Println("Block fetch: ", _blockNumber) + block, err := client.BlockByNumber(context.Background(), _blockNumber) + if err != nil { + return nil, err + } + return block, nil +} diff --git a/pkg/eth/eth_transaction.go b/pkg/eth/eth_transaction.go new file mode 100644 index 0000000..7cf2441 --- /dev/null +++ b/pkg/eth/eth_transaction.go @@ -0,0 +1,138 @@ +package eth + +import ( + "context" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +func (e *EthAccount) SignTx(tx *types.Transaction) (*types.Transaction, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return nil, err + } + + chainID, err := client.NetworkID(context.Background()) + if err != nil { + return nil, err + } + + signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), e.PrivateKey()) + if err != nil { + return nil, err + } + + return signedTx, nil +} + +func GetTransactionByHash(hash string) (*types.Receipt, error) { + fmt.Println("GetTransactionByHash", hash) + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return nil, err + } + _byte, err := common.ParseHexOrString(hash) + if err != nil { + return nil, err + } + _hash := common.BytesToHash(_byte) + tx, err := client.TransactionReceipt(context.Background(), _hash) + if err != nil { + return nil, err + } + return tx, nil +} + +func GetTransactionReceipt(txHash common.Hash) *types.Receipt { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return nil + } + receipt, err := client.TransactionReceipt(context.Background(), txHash) + if err != nil { + return nil + } + return receipt +} + +type ERC20TransferEvent struct { + From string + To string + Value string +} + +func decodeTransactionLogs(receipt *types.Receipt, contractABI *abi.ABI) ERC20TransferEvent { + + for _, vLog := range receipt.Logs { + // topic[0] is the event name + event, err := contractABI.EventByID(vLog.Topics[0]) + if err != nil { + return ERC20TransferEvent{} + } + + events := map[string]bool{ + "Transfer": true, + "TransferFrom": true, + } + + if !events[event.Name] { + continue + } + // topic[1:] is other indexed params in event + ev := ERC20TransferEvent{} + if len(vLog.Topics) > 1 { + ev.From = common.HexToAddress(vLog.Topics[1].Hex()).String() + ev.To = common.HexToAddress(vLog.Topics[2].Hex()).String() + } + + if len(vLog.Data) > 0 { + //fmt.Printf("Log Data in Hex: %s\n", hex.EncodeToString(vLog.Data)) + outputDataMap := make(map[string]interface{}) + err = contractABI.UnpackIntoMap(outputDataMap, event.Name, vLog.Data) + if err == nil { + ev.Value = outputDataMap["_value"].(*big.Int).String() + } + } + //fmt.Println(">>>>>", ev) + return ev + } + return ERC20TransferEvent{} +} + +func GetErc20Tranfer(txHash common.Hash) ERC20TransferEvent { + abi, err := abi.JSON(strings.NewReader(COMMON_ABI)) + receipt := GetTransactionReceipt(txHash) + if receipt == nil || len(receipt.Logs) == 0 || err != nil { + return ERC20TransferEvent{} + } + return decodeTransactionLogs(receipt, &abi) +} + +type TransactionData = map[string]interface{} + +func DecodeERC20Transfer(data []byte) (tx TransactionData, err error) { + abi, err := abi.JSON(strings.NewReader(COMMON_ABI)) + if err != nil { + return nil, err + } + tx = make(TransactionData) + if len(data) < 4 { + return nil, nil + } + methodSigData := data[:4] + method, err := abi.MethodById(methodSigData) + if err != nil { + return nil, err + } + inputsSigData := data[4:] + if err := method.Inputs.UnpackIntoMap(tx, inputsSigData); err != nil { + return nil, err + } + return tx, nil +} diff --git a/pkg/eth/eth_util.go b/pkg/eth/eth_util.go new file mode 100644 index 0000000..aed6662 --- /dev/null +++ b/pkg/eth/eth_util.go @@ -0,0 +1,17 @@ +package eth + +import ( + "math" + "math/big" +) + +func floatStringToWei(value string) *big.Int { + return floatStringToDec(value, 18) +} + +func floatStringToDec(value string, dec int) *big.Int { + f, _ := new(big.Float).SetString(value) + f.Mul(f, big.NewFloat(math.Pow10(dec))) + wei, _ := f.Int(nil) + return wei +} diff --git a/pkg/eth/eth_withdraw.go b/pkg/eth/eth_withdraw.go new file mode 100644 index 0000000..518d35a --- /dev/null +++ b/pkg/eth/eth_withdraw.go @@ -0,0 +1,230 @@ +package eth + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +func (e *EthAccount) WithdrawUSDTByComissionContract(contractAddr string, from string, to string, amount string, camount string) (string, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return "", err + } + + metodId := crypto.Keccak256Hash([]byte("chargeTransfer(address,address,address,uint256,uint256)")).Bytes()[:4] + + paddedTokenAddress := common.LeftPadBytes(common.HexToAddress(ethNode.usdtContractAddress).Bytes(), 32) + paddedFromAddress := common.LeftPadBytes(common.HexToAddress(from).Bytes(), 32) + paddedToAddress := common.LeftPadBytes(common.HexToAddress(to).Bytes(), 32) + + amountToSend := floatStringToDec(amount, ethNode.usdtContractDecimals) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + commissionToSend := floatStringToDec(camount, ethNode.usdtContractDecimals) + paddedCommission := common.LeftPadBytes(commissionToSend.Bytes(), 32) + + var data []byte + data = append(data, metodId...) + data = append(data, paddedTokenAddress...) + data = append(data, paddedFromAddress...) + data = append(data, paddedToAddress...) + data = append(data, paddedAmount...) + data = append(data, paddedCommission...) + + nonce, err := client.PendingNonceAt(context.Background(), e.address) + if err != nil { + return "", err + } + + gasLimit := uint64(210000) + gasTipCap, err := client.SuggestGasTipCap(context.Background()) + if err != nil { + return "", err + } + fmt.Println("Nonce", nonce) + chainId, _ := client.ChainID(context.Background()) + toAddress := common.HexToAddress(contractAddr) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainId, + To: &toAddress, + Nonce: nonce, + Value: big.NewInt(0), + Gas: gasLimit, + Data: data, + GasTipCap: big.NewInt(gasTipCap.Int64() * 2), + GasFeeCap: big.NewInt(40000000000), + }) + + signedTx, err := e.SignTx(tx) + if err != nil { + return "", err + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + return "", err + } + + return signedTx.Hash().Hex(), nil +} + +func (e *EthAccount) WithdrawUSDTBySpender(from string, to string, amount string) (string, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return "", err + } + + metodId := crypto.Keccak256Hash([]byte("transferFrom(address,address,uint256)")).Bytes()[:4] + + paddedFromAddress := common.LeftPadBytes(common.HexToAddress(from).Bytes(), 32) + paddedToAddress := common.LeftPadBytes(common.HexToAddress(to).Bytes(), 32) + + amountToSend := floatStringToDec(amount, ethNode.usdtContractDecimals) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + + var data []byte + data = append(data, metodId...) + data = append(data, paddedFromAddress...) + data = append(data, paddedToAddress...) + data = append(data, paddedAmount...) + + nonce, err := client.PendingNonceAt(context.Background(), e.address) + if err != nil { + return "", err + } + + gasLimit := uint64(60000) + gasTipCap, err := client.SuggestGasTipCap(context.Background()) + if err != nil { + return "", err + } + chainId, _ := client.ChainID(context.Background()) + toAddress := common.HexToAddress(ethNode.usdtContractAddress) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainId, + To: &toAddress, + Nonce: nonce, + Value: big.NewInt(0), + Gas: gasLimit, + Data: data, + GasTipCap: gasTipCap, + GasFeeCap: big.NewInt(20000000000), + }) + + signedTx, err := e.SignTx(tx) + if err != nil { + return "", err + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + return "", err + } + + return signedTx.Hash().Hex(), nil +} + +func (e *EthAccount) WithdrawUSDT(recipient string, amount string) (string, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return "", err + } + + metodId := crypto.Keccak256Hash([]byte("transfer(address,uint256)")).Bytes()[:4] + paddedAddress := common.LeftPadBytes(common.HexToAddress(recipient).Bytes(), 32) + + amountToSend := floatStringToDec(amount, ethNode.usdtContractDecimals) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + + var data []byte + data = append(data, metodId...) + data = append(data, paddedAddress...) + data = append(data, paddedAmount...) + + nonce, err := client.PendingNonceAt(context.Background(), e.address) + if err != nil { + return "", err + } + + gasLimit := uint64(60000) + gasTipCap, err := client.SuggestGasTipCap(context.Background()) + if err != nil { + return "", err + } + chainId, _ := client.ChainID(context.Background()) + toAddress := common.HexToAddress(ethNode.usdtContractAddress) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainId, + To: &toAddress, + Nonce: nonce, + Value: big.NewInt(0), + Gas: gasLimit, + Data: data, + GasTipCap: gasTipCap, + GasFeeCap: big.NewInt(20000000000), + }) + + signedTx, err := e.SignTx(tx) + if err != nil { + return "", err + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + return "", err + } + + return signedTx.Hash().Hex(), nil +} + +func (e *EthAccount) WithdrawETH(recipient string, amount string) (string, error) { + client, err := ethclient.Dial(ethNode.rpcNode) + if err != nil { + return "", err + } + + nonce, err := client.PendingNonceAt(context.Background(), e.Address()) + if err != nil { + return "", err + } + + gasTipCap, err := client.SuggestGasTipCap(context.Background()) + if err != nil { + return "", err + } + + value := floatStringToWei(amount) + gasLimit := uint64(21000) + toAddress := common.HexToAddress(recipient) + chainId, _ := client.ChainID(context.Background()) + + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainId, + To: &toAddress, + Nonce: nonce, + Value: value, + Gas: gasLimit, + Data: nil, + GasTipCap: gasTipCap, + GasFeeCap: big.NewInt(20000000000), + }) + + signedTx, err := e.SignTx(tx) + if err != nil { + fmt.Println("sign error", err) + return "", err + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + fmt.Println("send tx", err) + return "", err + } + + return signedTx.Hash().Hex(), nil +} diff --git a/pkg/locker/keys.go b/pkg/locker/keys.go new file mode 100644 index 0000000..2496890 --- /dev/null +++ b/pkg/locker/keys.go @@ -0,0 +1,4 @@ +package locker + +const DEFAULT_PRIVATE_KEY string = "TUFbZs63+FV+9KLgmoHgT1VK9YMuGUn///uqfrbtxx4MywtWpkPAS/QVTnSlwERxdQR9hIXR8xMAavWJ5Ix/ZLizLXVhhX4w1w7FE4SKmMewgUIK7NwlWWgDKIGlRTQlCOiOal6g3H4Mp6ndQGwNK8zo3NVlhekAKX57v8zrAvK6gWL/P3lXAwZVlSKxGREHZb7bX2mVWv4tSXFEKp1p2pzlV/qWCTcn67mSygotkk2ExB9Knm9d0V2l+klzbQHdx38RhwFvfBetf84w8a5Gb+Fv4fwvi6Um9cbcGtW1IvX0cb24YG2MfJKVKEAI9hhfGDJZZ99hzI2nljY2qu374QoKpXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww==" +const DEFAULT_PUBLIC_KEY string = "pXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww==" diff --git a/pkg/locker/settings.go b/pkg/locker/settings.go new file mode 100644 index 0000000..4c28b53 --- /dev/null +++ b/pkg/locker/settings.go @@ -0,0 +1,38 @@ +package locker + +import ( + "fmt" + "os" + "strings" +) + +const PRIVATE_PREFIX = "PRIVATE__" + +type PrivateSettings = map[string]string + +var PrivateSettingsInstance *PrivateSettings + +func Settings() *PrivateSettings { + if PrivateSettingsInstance != nil { + return PrivateSettingsInstance + } + settings := make(PrivateSettings) + for _, env := range os.Environ() { + if len(env) > len(PRIVATE_PREFIX) && env[:len(PRIVATE_PREFIX)] == PRIVATE_PREFIX { + key := env[len(PRIVATE_PREFIX):strings.Index(env, "=")] + value := os.Getenv(env[:strings.Index(env, "=")]) + settings[key] = value + } + } + return &settings +} + +func GetPrivateEnv(key string) string { + settings := Settings() + value := (*settings)[key] + if value == "" { + fmt.Println("No private variable found for " + key) + return "" + } + return RSABase64Value(value).Decrypt() +} diff --git a/pkg/locker/types.go b/pkg/locker/types.go new file mode 100644 index 0000000..b23b22d --- /dev/null +++ b/pkg/locker/types.go @@ -0,0 +1,39 @@ +package locker + +import ( + "os" + + "custodial/pkg/crypto" +) + +var PUBLIC_KEY = getenv("PUBLIC_KEY", DEFAULT_PUBLIC_KEY) +var PRIVATE_KEY = getenv("PRIVATE_KEY", DEFAULT_PRIVATE_KEY) +var PrivKey crypto.RSAKey = crypto.RSAKey(PRIVATE_KEY) +var PubKey crypto.RSAKey = crypto.RSAKey(PUBLIC_KEY) + +type RSABase64Value string +type StringValue string + +func (v RSABase64Value) String() string { + return string(v) +} + +func (v RSABase64Value) Decrypt() string { + return PrivKey.DecryptToString(v.String()) +} + +func (v StringValue) String() string { + return string(v) +} + +func (v StringValue) Encrypt() RSABase64Value { + return RSABase64Value(PubKey.EncryptToString(v.String())) +} + +func getenv(key, defaultValue string) string { + value := os.Getenv(key) + if len(value) == 0 { + return defaultValue + } + return value +} diff --git a/pkg/rest/account_v1.go b/pkg/rest/account_v1.go new file mode 100644 index 0000000..9159862 --- /dev/null +++ b/pkg/rest/account_v1.go @@ -0,0 +1,18 @@ +package rest + +import ( + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +func AccountRestV1(r *gin.RouterGroup) { + store.Init() + + r.POST("/balance", AccountBalanceV1) + r.POST("/hd/balance", AccountHDBalanceV1) + r.POST("/create", CreateAccountV1) + r.GET("/spender-status", AccountSpenderStatusV1) + r.GET("/count", AccountCountV1) + r.POST("/list", AccountListV1) +} diff --git a/pkg/rest/account_v1_balance.go b/pkg/rest/account_v1_balance.go new file mode 100644 index 0000000..19cdd2a --- /dev/null +++ b/pkg/rest/account_v1_balance.go @@ -0,0 +1,153 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type AccountBalanceRequest struct { + AccountID string `json:"account_id"` +} + +type AccountHDBalanceRequest struct { + Index int `json:"index"` +} + +type AccountBalanceResponse struct { + AccountID string `json:"account_id"` + Label string `json:"label"` + Tron trc.TronAccountBalance + Ethereum eth.EthAccountBalance + TronSpender string `json:"tron_spender"` + EthSpender string `json:"eth_spender"` + TronHdIndex int `json:"tron_hd_index"` + EthHdIndex int `json:"eth_hd_index"` + EthAllowance string `json:"eth_allowance"` + TronAllowance string `json:"tron_allowance"` +} + +type AccountHDBalanceResponse struct { + Tron trc.TronAccountBalance + Ethereum eth.EthAccountBalance + EthAllowance string `json:"eth_allowance"` + TronAllowance string `json:"tron_allowance"` +} + +// @BasePath /api/v1 +// +// AccountBalance godoc +// +// @Summary Get account balance +// @Schemes + +// @Description Get account balance for all currencies +// @Tags Account +// @Accept json +// @Security ApiKeyAuth +// @Produce json +// @Param message body AccountBalanceRequest true "Account Info" +// @Success 200 {object} AccountBalanceResponse +// @Failure 400 {object} ErrorResponse +// @Router /account/balance [post] +func AccountBalanceV1(c *gin.Context) { + var req AccountBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbEthAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbTronAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tronBalance, err := trc.TronBalance(dbTronAccount.GetAddress()) + tronSpender, _ := trc.GetSpender() + tronAllowance, _ := trc.TronAllowance(dbTronAccount.GetAddress(), tronSpender.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + ethBalance, err := eth.EthBalance(dbEthAccount.GetAddress()) + ethSpender, _ := eth.GetSpender() + ethAllowance, _ := eth.EthAllowance(dbEthAccount.GetAddress(), ethSpender.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "account_id": req.AccountID, + "label": dbEthAccount.GetLabel(), + "tron": tronBalance, + "ethereum": ethBalance, + "tron_spender": dbTronAccount.GetSpender(), + "eth_spender": dbEthAccount.GetSpender(), + "tron_hd_index": dbTronAccount.GetHDIndex(), + "eth_hd_index": dbEthAccount.GetHDIndex(), + "eth_allowance": ethAllowance, + "tron_allowance": tronAllowance, + }) +} + +// @BasePath /api/v1 +// +// AccountBalance godoc +// +// @Summary Get account balance +// @Schemes + +// @Description Get account balance for all currencies +// @Tags Account +// @Accept json +// @Security ApiKeyAuth +// @Produce json +// @Param message body AccountHDBalanceRequest true "Account Info" +// @Success 200 {object} AccountHDBalanceResponse +// @Failure 400 {object} ErrorResponse +// @Router /account/hd/balance [post] +func AccountHDBalanceV1(c *gin.Context) { + var req AccountHDBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + keys := GetPrivKeys(req.Index) + + tronBalance, err := trc.TronBalance(keys.TRONAddress) + tronSpender, _ := trc.GetSpender() + tronAllowance, _ := trc.TronAllowance(keys.TRONAddress, tronSpender.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + ethBalance, err := eth.EthBalance(keys.ETHAddress) + ethSpender, _ := eth.GetSpender() + ethAllowance, _ := eth.EthAllowance(keys.ETHAddress, ethSpender.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "tron": tronBalance, + "ethereum": ethBalance, + "eth_allowance": ethAllowance, + "tron_allowance": tronAllowance, + }) +} diff --git a/pkg/rest/account_v1_count.go b/pkg/rest/account_v1_count.go new file mode 100644 index 0000000..f38b52d --- /dev/null +++ b/pkg/rest/account_v1_count.go @@ -0,0 +1,41 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type AccountCountResponse struct { + Count int `json:"count"` +} + +// @BasePath /api/v1 +// +// AccountCountV1 godoc +// +// @Summary Count accounts +// @Schemes + +// @Description Count accounts +// @Tags Account +// @Accept json +// @Security ApiKeyAuth +// @Produce json +// @Success 200 {object} AccountCountResponse +// @Failure 400 {object} ErrorResponse +// @Router /account/count [get] +func AccountCountV1(c *gin.Context) { + + count, err := store.CountAccounts(store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "count": count, + }) +} diff --git a/pkg/rest/account_v1_create.go b/pkg/rest/account_v1_create.go new file mode 100644 index 0000000..2d42e2f --- /dev/null +++ b/pkg/rest/account_v1_create.go @@ -0,0 +1,123 @@ +package rest + +import ( + "fmt" + "net/http" + "sync" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type CreateAccountRequest struct { + AccountID string `json:"account_id"` + Label string `json:"label"` + Index int `json:"index"` +} + +type CreateAccountResponse struct { + AccountID string `json:"account_id"` + Label string `json:"label"` + Index int `json:"index"` + TronAddress string `json:"tron_address"` + EthAddress string `json:"eth_address"` +} + +var amu sync.Mutex + +// @BasePath /api/v1 +// +// CreateAccount godoc +// +// @Summary Create a new user account +// @Schemes +// @Description Create a new user account identified by account_id and index (incremental number) +// @Tags Account +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body CreateAccountRequest true "Account Info" +// @Success 200 {object} CreateAccountResponse +// @Failure 400 {object} ErrorResponse +// @Router /account/create [post] +func CreateAccountV1(c *gin.Context) { + amu.Lock() + defer amu.Unlock() + + var req CreateAccountRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + fmt.Println("CreateAccountV1", req.AccountID) + if req.AccountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "account_id is required"}) + return + } + + if req.Index < 2 || req.Index >= store.MAX_ACCOUNT_INDEX { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("index is invalid: min index must is 2, max index is %s", store.MAX_ACCOUNT_INDEX-1), + }) + return + } + + exEth, _ := store.GetAccount(req.AccountID, store.Ethereum) + exTrc, _ := store.GetAccount(req.AccountID, store.Tron) + fmt.Println("exEth", exEth) + fmt.Println("exTrc", exTrc) + if exEth != nil && exTrc != nil { + if exEth.GetAddress() != "" || exTrc.GetAddress() != "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "account with this id already exists"}) + return + } + } + + nextHdIndex := int(req.Index) + ethAccount, err := eth.DeriveAccount(nextHdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tronAccount, err := trc.DeriveAccount(nextHdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbEthAccount := store.EthereumAccount{ + AccountID: req.AccountID, + Label: req.Label, + Address: ethAccount.Address().String(), + HDIndex: nextHdIndex, + } + err = store.CreateAccount(&dbEthAccount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbTronAccount := store.TronAccount{ + AccountID: req.AccountID, + Label: req.Label, + Address: tronAccount.Address(), + HDIndex: nextHdIndex, + } + err = store.CreateAccount(&dbTronAccount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "account_id": req.AccountID, + "label": req.Label, + "index": nextHdIndex, + "tron_address": tronAccount.Address(), + "eth_address": ethAccount.Address().String(), + }) +} diff --git a/pkg/rest/account_v1_list.go b/pkg/rest/account_v1_list.go new file mode 100644 index 0000000..ca4f0a6 --- /dev/null +++ b/pkg/rest/account_v1_list.go @@ -0,0 +1,61 @@ +package rest + +import ( + "fmt" + "net/http" + + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type AccountListRequest struct { + Offset int `json:"offset"` + Limit int `json:"limit"` +} + +// @BasePath /api/v1 +// +// AccountListV1 godoc +// +// @Summary List all accounts +// @Schemes +// @Description List accounts paginating by (limit, offset) +// @Tags Account +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body AccountListRequest true "Paging Info" +// @Success 200 {object} []CreateAccountResponse +// @Failure 400 {object} ErrorResponse +// +// @Router /account/list [post] +func AccountListV1(c *gin.Context) { + + var req AccountListRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + eth, err := store.ListAccounts(store.Ethereum, req.Offset, req.Limit) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + fmt.Println("eth", eth) + + merge := []CreateAccountResponse{} + for _, account := range eth { + + merge = append(merge, CreateAccountResponse{ + AccountID: account.GetAccountID(), + Label: account.GetLabel(), + Index: account.GetHDIndex(), + TronAddress: GetPrivKeys(account.GetHDIndex()).TRONAddress, + EthAddress: account.GetAddress(), + }) + } + + c.JSON(http.StatusOK, merge) +} diff --git a/pkg/rest/account_v1_spender.go b/pkg/rest/account_v1_spender.go new file mode 100644 index 0000000..9c0b742 --- /dev/null +++ b/pkg/rest/account_v1_spender.go @@ -0,0 +1,62 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type SpenderBalanceResponse struct { + Tron trc.TronAccountBalance + Ethereum eth.EthAccountBalance + AccountsCount int `json:"accounts_count"` +} + +// @BasePath /api/v1 +// +// AccountBalance godoc +// +// @Summary Check spender status +// @Schemes + +// @Description Check spender accounts +// @Tags Account +// @Accept json +// @Security ApiKeyAuth +// @Produce json +// @Success 200 {object} SpenderBalanceResponse +// @Failure 400 {object} ErrorResponse +// @Router /account/spender-status [get] +func AccountSpenderStatusV1(c *gin.Context) { + + ethAccount, _ := eth.GetSpender() + trcAccount, _ := trc.GetSpender() + + tronBalance, err := trc.TronBalance(trcAccount.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + ethBalance, err := eth.EthBalance(ethAccount.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + count, err := store.CountAccounts(store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "tron": tronBalance, + "ethereum": ethBalance, + "accounts_count": count, + }) +} diff --git a/pkg/rest/auth_middleware.go b/pkg/rest/auth_middleware.go new file mode 100644 index 0000000..635ab5c --- /dev/null +++ b/pkg/rest/auth_middleware.go @@ -0,0 +1,21 @@ +package rest + +import ( + "net/http" + + "custodial/pkg/locker" + + "github.com/gin-gonic/gin" +) + +func AuthMiddleware() gin.HandlerFunc { + locker.Settings() + apiMasterKey := locker.GetPrivateEnv("API_MASTER_KEY") + return func(c *gin.Context) { + if c.Request.Header.Get("Authorization") != apiMasterKey { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + c.Next() + } +} diff --git a/pkg/rest/echo_v1.go b/pkg/rest/echo_v1.go new file mode 100644 index 0000000..15af628 --- /dev/null +++ b/pkg/rest/echo_v1.go @@ -0,0 +1,18 @@ +package rest + +import ( + "fmt" + "net/http" + + gin "github.com/gin-gonic/gin" +) + +func EchoV1(c *gin.Context) { + var req gin.H + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + fmt.Println("EchoV1: ", req) + c.JSON(http.StatusOK, req) +} diff --git a/pkg/rest/eth_v1.go b/pkg/rest/eth_v1.go new file mode 100644 index 0000000..dc16a13 --- /dev/null +++ b/pkg/rest/eth_v1.go @@ -0,0 +1,33 @@ +package rest + +import ( + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +func EthRestV1(r *gin.RouterGroup) { + eth.InitMasterFromPrivateSettings() + store.Init() + + var node eth.EthNode + + _, err := node.Init() + if err != nil { + panic(err) + } + + r.POST("/create-account", EthCreateAccountV1) + r.POST("/address-balance", EthAddressBalanceV1) + r.POST("/account-balance", EthAccountBalanceV1) + r.POST("/withdraw-usdt", EthWithdrawUsdtV1) + r.POST("/withdraw-eth", EthWithdrawEthV1) + r.POST("/approve-usdt-spender", EthApproveUsdtSpenderV1) + r.POST("/approve-usdt-contract", EthApproveUsdtContractV1) + r.POST("/withdraw-usdt-by-spender", EthWithdrawUsdtBySpenderV1) + r.POST("/withdraw-usdt-by-contract", EthWithdrawUsdtByContractV1) + r.POST("/recharge-eth-by-spender", EthRechargeEthBySpenderV1) + r.POST("/approve-usdt-custody", EthApproveUsdtCustodyV1) + r.POST("/tx-info", EthTransactionInfoV1) +} diff --git a/pkg/rest/eth_v1_account.go b/pkg/rest/eth_v1_account.go new file mode 100644 index 0000000..de9986b --- /dev/null +++ b/pkg/rest/eth_v1_account.go @@ -0,0 +1,76 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type CreateEthAccountRequest struct { + AccountID string `json:"account_id"` +} + +type CreateEthAccountResponse struct { + Address string `json:"address"` +} + +// @BasePath /api/v1 +// +// EthCreateAccount godoc +// +// @Summary Create a new account +// @Schemes +// @Description Create a new etherum account identified by account_id +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body CreateEthAccountRequest true "Account Info" +// @Success 200 {object} CreateEthAccountResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/create-account [post] +func EthCreateAccountV1(c *gin.Context) { + var req CreateEthAccountRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.AccountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "account_id is required"}) + return + } + + last, err := store.LastAccount(store.Ethereum) + if err != nil && err.Error() != "record not found" { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + lastHdIndex := 0 + if last != nil { + lastHdIndex = last.GetHDIndex() + 1 + } + + account, err := eth.DeriveAccount(lastHdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + ethAccount := store.EthereumAccount{ + AccountID: req.AccountID, + Address: account.Address().String(), + HDIndex: lastHdIndex, + } + err = store.CreateAccount(ðAccount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"address": account.Address()}) +} diff --git a/pkg/rest/eth_v1_approve.go b/pkg/rest/eth_v1_approve.go new file mode 100644 index 0000000..9d5dccd --- /dev/null +++ b/pkg/rest/eth_v1_approve.go @@ -0,0 +1,148 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type ETHApproveUSDTRequest struct { + AccountID string `json:"account_id"` +} + +type ETHApproveContractRequest struct { + AccountID string `json:"account_id"` + ConractAddress string `json:"contract_address"` +} + +type ETHApproveResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// EthApproveUsdtV1 godoc +// +// @Summary Approve USDT spender +// @Schemes +// @Description Approve USDT spender address for account +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body ETHApproveUSDTRequest true "Account Info" +// @Success 200 {object} ETHApproveResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/approve-usdt-spender [post] +func EthApproveUsdtSpenderV1(c *gin.Context) { + var req ETHApproveUSDTRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if storedAccount.GetSpender() != "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Spender already approved"}) + return + } + + ethSpender, _ := eth.GetSpender() + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := eth.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := eth.EthBalance(account.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.ETHRaw < 1000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough ETH to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(ethSpender.Address().String(), approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.EthereumAccount).Spender = ethSpender.Address().String() + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} + +// @BasePath /api/v1 +// +// EthApproveUsdtV1 godoc +// +// @Summary Approve USDT spender contract +// @Schemes +// @Description Approve USDT spender contract address for account +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body ETHApproveContractRequest true "Account Info" +// @Success 200 {object} ETHApproveResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/approve-usdt-contract [post] +func EthApproveUsdtContractV1(c *gin.Context) { + var req ETHApproveContractRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := eth.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := eth.EthBalance(account.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.ETHRaw < 1000000000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough ETH to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(req.ConractAddress, approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.EthereumAccount).Spender = req.ConractAddress + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/eth_v1_balance.go b/pkg/rest/eth_v1_balance.go new file mode 100644 index 0000000..ee49f3b --- /dev/null +++ b/pkg/rest/eth_v1_balance.go @@ -0,0 +1,85 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type EthAddressBalanceRequest struct { + Address string `json:"address"` +} + +type EthAccountBalanceRequest struct { + AccountID string `json:"account_id"` +} + +// @BasePath /api/v1 +// +// EthAddressBalance godoc +// +// @Summary Get eth address balance +// @Schemes +// @Description Get address balance +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body EthAddressBalanceRequest true "Account Info" +// @Success 200 {object} eth.EthAccountBalance +// @Failure 400 {object} ErrorResponse +// @Router /eth/address-balance [post] +func EthAddressBalanceV1(c *gin.Context) { + var req TronAddressBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := eth.EthBalance(req.Address) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, balance) +} + +// @BasePath /api/v1 +// +// EthAccountBalance godoc +// +// @Summary Get account balance +// @Schemes +// @Description Get account balance +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body EthAccountBalanceRequest true "Account Info" +// @Success 200 {object} eth.EthAccountBalance +// @Failure 400 {object} ErrorResponse +// @Router /eth/account-balance [post] +func EthAccountBalanceV1(c *gin.Context) { + var req EthAccountBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := eth.EthBalance(storedAccount.GetAddress()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, balance) +} diff --git a/pkg/rest/eth_v1_custodial.go b/pkg/rest/eth_v1_custodial.go new file mode 100644 index 0000000..8cb2364 --- /dev/null +++ b/pkg/rest/eth_v1_custodial.go @@ -0,0 +1,84 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type ETHApproveCustodyRequest struct { + Address string `json:"address"` +} + +type ETHApproveCustodyResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// EthApproveUsdtCustodyV1 godoc +// +// @Summary Approve USDT Castody for Spender Account +// @Schemes +// @Description Approve USDT spender address for account +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body ETHApproveCustodyRequest true "Account Info" +// @Success 200 {object} ETHApproveCustodyResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/approve-usdt-custody [post] +func EthApproveUsdtCustodyV1(c *gin.Context) { + var req ETHApproveCustodyRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccountByAddress(req.Address, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // if storedAccount.GetSpender() != "" { + // c.JSON(http.StatusBadRequest, gin.H{"error": "Spender already approved"}) + // return + // } + + ethSpender, _ := eth.GetSpender() + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := eth.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := eth.EthBalance(account.Address().String()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.ETHRaw < 1000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough ETH to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(ethSpender.Address().String(), approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.EthereumAccount).Spender = ethSpender.Address().String() + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/eth_v1_recharge.go b/pkg/rest/eth_v1_recharge.go new file mode 100644 index 0000000..8d720a3 --- /dev/null +++ b/pkg/rest/eth_v1_recharge.go @@ -0,0 +1,63 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type RechargeETHRequest struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` +} + +type RechargeETHResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// EthRechargeEthBySpenderV1 godoc +// +// @Summary Add ETH from spender account +// @Schemes +// @Description Add ETH funds to the account from the spender +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body RechargeETHRequest true "Account Info" +// @Success 200 {object} RechargeETHResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/recharge-eth-by-spender [post] +func EthRechargeEthBySpenderV1(c *gin.Context) { + var req RechargeETHRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spender, err := eth.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spender.WithdrawETH(storedAccount.GetAddress(), req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/eth_v1_transaction.go b/pkg/rest/eth_v1_transaction.go new file mode 100644 index 0000000..9e8ee69 --- /dev/null +++ b/pkg/rest/eth_v1_transaction.go @@ -0,0 +1,62 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + + gin "github.com/gin-gonic/gin" +) + +type EthTransactionInfoRequest struct { + TxID string `json:"tx_id"` +} + +type EthTransactionInfoResponse struct { + Id string `json:"id"` + Status string `json:"status"` +} + +// @BasePath /api/v1 +// +// AddressBalance godoc +// +// @Summary Get transaction info +// @Schemes +// @Description Get transaction info +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body EthTransactionInfoRequest true "Tx Info" +// @Success 200 {object} EthTransactionInfoResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/tx-info [post] +func EthTransactionInfoV1(c *gin.Context) { + var req EthTransactionInfoRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tx, err := eth.GetTransactionByHash(req.TxID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + status := "pending" + if tx == nil { + status = "pending" + } else if tx.Status == 1 { + status = "success" + } else if tx.Status == 0 { + status = "failed" + } + res := EthTransactionInfoResponse{ + Id: req.TxID, + Status: status, + } + + c.JSON(http.StatusOK, res) +} diff --git a/pkg/rest/eth_v1_withdraw.go b/pkg/rest/eth_v1_withdraw.go new file mode 100644 index 0000000..20a82a4 --- /dev/null +++ b/pkg/rest/eth_v1_withdraw.go @@ -0,0 +1,230 @@ +package rest + +import ( + "net/http" + + eth "custodial/pkg/eth" + store "custodial/pkg/store" + + gin "github.com/gin-gonic/gin" +) + +type WithdrawEthRequest struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` + To string `json:"to"` +} + +type WithdrawEthResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +type WithdrawUSDTBySpenderRequest struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` + ComissionAmount string `json:"comission_amount"` + To string `json:"to"` +} + +type WithdrawSpenderResponse struct { + Status string `json:"status"` + TXIDWithdrawal string `json:"txid_withdrawal"` + TXIDComission string `json:"txid_commission"` +} + +// @BasePath /api/v1 +// +// EthWithdrawUsdtV1 godoc +// +// @Summary Withdraw USDT +// @Schemes +// @Description Withdraw usdt funds from etherum account +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawEthRequest true "Account Info" +// @Success 200 {object} WithdrawEthResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/withdraw-usdt [post] +func EthWithdrawUsdtV1(c *gin.Context) { + var req WithdrawEthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hdIndex := storedAccount.GetHDIndex() + account, err := eth.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := account.WithdrawUSDT(req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} + +// @BasePath /api/v1 +// +// EthWithdrawUsdtBySpenderV1 godoc +// +// @Summary Withdraw USDT by commission contract +// @Schemes +// @Description Withdraw usdt funds from etherum account using approved spender contract +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WidtdrawUSDTByContract true "Account Info" +// @Success 200 {object} WithdrawEthResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/withdraw-usdt-by-contract [post] +func EthWithdrawUsdtByContractV1(c *gin.Context) { + var req WidtdrawUSDTByContract + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spender, err := eth.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spender.WithdrawUSDTByComissionContract(req.ContractAddress, storedAccount.GetAddress(), req.To, req.Amount, req.ComissionAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "txid": txid, + "status": "ok", + }) +} + +// @BasePath /api/v1 +// +// EthWithdrawUsdtBySpenderV1 godoc +// +// @Summary Withdraw USDT by Spender +// @Schemes +// @Description Withdraw usdt funds from etherum account using approved spender +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawUSDTBySpenderRequest true "Account Info" +// @Success 200 {object} WithdrawSpenderResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/withdraw-usdt-by-spender [post] +func EthWithdrawUsdtBySpenderV1(c *gin.Context) { + var req WithdrawUSDTBySpenderRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spender, err := eth.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spender.WithdrawUSDTBySpender(storedAccount.GetAddress(), req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + comission := "" + if req.ComissionAmount != "" && req.ComissionAmount != "0" { + txidComission, err := spender.WithdrawUSDTBySpender( + storedAccount.GetAddress(), + spender.Address().String(), + req.ComissionAmount, + ) + if err != nil { + comission = err.Error() + } else { + comission = txidComission + } + } + + c.JSON(http.StatusOK, gin.H{ + "txid_withdrawal": txid, + "txid_commission": comission, + "status": "ok", + }) +} + +// @BasePath /api/v1 +// +// EthWithdrawV1 godoc +// +// @Summary Withdraw ETH +// @Schemes +// @Description Withdraw ETH funds from ethereum account +// @Tags Ethereum +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawEthRequest true "Account Info" +// @Success 200 {object} WithdrawEthResponse +// @Failure 400 {object} ErrorResponse +// @Router /eth/withdraw-eth [post] +func EthWithdrawEthV1(c *gin.Context) { + var req WithdrawEthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Ethereum) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hdIndex := storedAccount.GetHDIndex() + account, err := eth.DeriveAccount(hdIndex) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := account.WithdrawETH(req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/export_v1.go b/pkg/rest/export_v1.go new file mode 100644 index 0000000..003aa15 --- /dev/null +++ b/pkg/rest/export_v1.go @@ -0,0 +1,144 @@ +package rest + +import ( + "net/http" + + hex "encoding/hex" + + gin "github.com/gin-gonic/gin" + + eth "custodial/pkg/eth" + tron "custodial/pkg/tron" + + crypto "github.com/ethereum/go-ethereum/crypto" +) + +type ExportV1Request struct { + Index int `json:"index"` +} + +type ExportV1Response struct { + ETHAddress string `json:"eth_address"` + TRONAddress string `json:"tron_address"` + ETHPrivateKey string `json:"eth_private_key"` + TRONPrivateKey string `json:"tron_private_key"` +} + +func GetPrivKeys(idx int) ExportV1Response { + eth.InitMasterFromPrivateSettings() + tron.InitMasterFromPrivateSettings() + + ethAccount, _ := eth.DeriveAccount(idx) + tronAccount, _ := tron.DeriveAccount(idx) + + ethPrivateKey := crypto.FromECDSA(ethAccount.PrivateKey()) + tronPrivateKey := crypto.FromECDSA(tronAccount.PrivateKey().ToECDSA()) + + // PRINT PRIVATE KEYS FOR THE ACCOUNTS + // fmt.Println("") + // fmt.Println("ETH Private Key: ", hex.EncodeToString(ethPrivateKey), "Address:", ethAccount.Address()) + // fmt.Println("TRON Private Key: ", hex.EncodeToString(tronPrivateKey), "Address:", tronAccount.Address()) + // fmt.Println("") + return ExportV1Response{ + ETHAddress: ethAccount.Address().String(), + TRONAddress: tronAccount.Address(), + ETHPrivateKey: hex.EncodeToString(ethPrivateKey), + TRONPrivateKey: hex.EncodeToString(tronPrivateKey), + } +} + +// @BasePath /api/v1 +// +// ExportV1 godoc +// +// @Summary Export private keys for the given index +// @Schemes +// @Description Export private keys for the given index (uint32) +// @Tags Utils +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body ExportV1Request true "Account Info" +// @Success 200 {object} ExportV1Response +// @Failure 400 {object} ErrorResponse +// @Router /export [post] +func ExportV1(c *gin.Context) { + var req ExportV1Request + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.Index < 2 { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "index is invalid: min index must is 2", + }) + return + } + + res := GetPrivKeys(req.Index) + + c.JSON(http.StatusOK, res) +} + +type ExportBatchV1Request struct { + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +// @BasePath /api/v1 +// +// ExportV1 godoc +// +// @Summary Export private keys for the given range +// @Schemes +// @Description Export private keys for the given range (uint32) +// @Tags Utils +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body ExportBatchV1Request true "Paging Info" +// @Success 200 {object} []ExportV1Response +// @Failure 400 {object} ErrorResponse +// @Router /export-batch [post] +func ExportBatchV1(c *gin.Context) { + var req ExportBatchV1Request + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.Offset < 2 { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "index is invalid: min index must is 2", + }) + return + } + + var res []ExportV1Response + for i := req.Offset; i < req.Offset+req.Limit; i++ { + res = append(res, GetPrivKeys(i)) + } + + c.IndentedJSON(http.StatusOK, res) +} + +// @BasePath /api/v1 +// +// ExportSpenderKeysV1 godoc +// +// @Summary Export spender private keys +// @Schemes + +// @Description Export spender private keys +// @Tags Utils +// @Accept json +// @Security ApiKeyAuth +// @Produce json +// @Success 200 {object} ExportV1Response +// @Failure 400 {object} ErrorResponse +// @Router /export-spender-keys [get] +func ExportSpenderKeysV1(c *gin.Context) { + keys := GetPrivKeys(eth.MAX_ACCOUNT_INDEX) + c.IndentedJSON(http.StatusOK, keys) +} diff --git a/pkg/rest/routes.go b/pkg/rest/routes.go new file mode 100644 index 0000000..11d9346 --- /dev/null +++ b/pkg/rest/routes.go @@ -0,0 +1,37 @@ +package rest + +import ( + docs "custodial/docs" + + gin "github.com/gin-gonic/gin" + swaggerfiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +func Router() *gin.Engine { + router := gin.Default() + router.LoadHTMLFiles("cmd/envcrypt.html") + docs.SwaggerInfo.BasePath = "/api/v1" + + utils := router.Group("/utils") + utils.POST("/echo", EchoV1) + utils.GET("/envcrypt", func(c *gin.Context) { + c.HTML(200, "envcrypt.html", nil) + }) + + v1 := router.Group("/api/v1") + v1.Use(AuthMiddleware()) + v1.Use(SyncMiddleware()) + + v1.POST("/export", ExportV1) + v1.POST("/export-batch", ExportBatchV1) + v1.GET("/export-spender-keys", ExportSpenderKeysV1) + + AccountRestV1(v1.Group("/account")) + TronRestV1(v1.Group("/tron")) + EthRestV1(v1.Group("/eth")) + + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) + + return router +} diff --git a/pkg/rest/sync_middleware.go b/pkg/rest/sync_middleware.go new file mode 100644 index 0000000..ae0ab31 --- /dev/null +++ b/pkg/rest/sync_middleware.go @@ -0,0 +1,30 @@ +package rest + +import ( + "fmt" + "net/http" + "github.com/gin-gonic/gin" +) + +var syncState bool = false + +func SyncMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + qs := c.Request.URL.Query() + for key, values := range qs { + fmt.Printf("key = %v, value(s) = %v\n", key, values) + if key == "sync_state" { + if values[0] == "true" { + syncState = true + } else { + syncState = false + } + } + } + if syncState { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "sync state"}) + return + } + c.Next() + } +} diff --git a/pkg/rest/tron_v1.go b/pkg/rest/tron_v1.go new file mode 100644 index 0000000..ad14848 --- /dev/null +++ b/pkg/rest/tron_v1.go @@ -0,0 +1,40 @@ +package rest + +import ( + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +// TLrJqNptmYujFBj6UkQ49ARjdEU8zK6GzB + +type ErrorResponse struct { + Error string `json:"error"` +} + +func TronRestV1(r *gin.RouterGroup) { + trc.InitMasterFromPrivateSettings() + store.Init() + + var node trc.TronNode + + _, err := node.Init() + if err != nil { + panic(err) + } + + r.POST("/create-account", TronCreateAccountV1) + r.POST("/address-balance", TronAddressBalanceV1) + r.POST("/account-balance", TronAccountBalanceV1) + r.POST("/withdraw-usdt", TronWithdrawUsdtV1) + r.POST("/withdraw-trx", TronWithdrawTrxV1) + r.POST("/approve-usdt-spender", TronApproveUsdtSpenderV1) + r.POST("/approve-usdt-contract", TronApproveUsdtContractV1) + r.POST("/withdraw-usdt-by-spender", TronWithdrawUsdtBySpenderV1) + r.POST("/withdraw-usdt-by-contract", TronWithdrawUsdtByContractV1) + r.POST("/recharge-trx-by-spender", TronRechargeTrxBySpenderV1) + r.POST("/approve-usdt-custody", TronApproveUsdtCustodyV1) + r.POST("/tx-info", TronTransactionInfoV1) + +} diff --git a/pkg/rest/tron_v1_account.go b/pkg/rest/tron_v1_account.go new file mode 100644 index 0000000..ba6bce7 --- /dev/null +++ b/pkg/rest/tron_v1_account.go @@ -0,0 +1,76 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type CreateTronAccountRequest struct { + AccountID string `json:"account_id"` +} + +type CreateTronAccountResponse struct { + Address string `json:"address"` +} + +// @BasePath /api/v1 +// +// TronCreateAccount godoc +// +// @Summary Create a new account +// @Schemes +// @Description Create a new tron account identified by account_id +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body CreateTronAccountRequest true "Account Info" +// @Success 200 {object} CreateTronAccountResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/create-account [post] +func TronCreateAccountV1(c *gin.Context) { + var req CreateTronAccountRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.AccountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "account_id is required"}) + return + } + + last, err := store.LastAccount(store.Tron) + if err != nil && err.Error() != "record not found" { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + lastHdIndex := 0 + if last != nil { + lastHdIndex = last.GetHDIndex() + 1 + } + + account, err := trc.DeriveAccount(lastHdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + trxAccount := store.TronAccount{ + AccountID: req.AccountID, + Address: account.Address(), + HDIndex: lastHdIndex, + } + err = store.CreateAccount(&trxAccount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"address": account.Address()}) +} diff --git a/pkg/rest/tron_v1_approve.go b/pkg/rest/tron_v1_approve.go new file mode 100644 index 0000000..6e7ca3a --- /dev/null +++ b/pkg/rest/tron_v1_approve.go @@ -0,0 +1,148 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type TRXAprroveUSDTRequest struct { + AccountID string `json:"account_id"` +} + +type TRXAprroveContractRequest struct { + AccountID string `json:"account_id"` + ConractAddress string `json:"contract_address"` +} + +type TRXApproveUSDTResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// TronApproveUsdtV1 godoc +// +// @Summary Approve USDT Spender +// @Schemes +// @Description Approve USDT spender for account +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TRXAprroveUSDTRequest true "Account Info" +// @Success 200 {object} TRXApproveUSDTResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/approve-usdt-spender [post] +func TronApproveUsdtSpenderV1(c *gin.Context) { + var req TRXAprroveUSDTRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // if storedAccount.GetSpender() != "" { + // c.JSON(http.StatusBadRequest, gin.H{"error": "Spender already approved"}) + // return + // } + + trcSpender, _ := trc.GetSpender() + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := trc.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := trc.TronBalance(account.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.TRXRaw < 1000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough TRX to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(trcSpender.Address(), approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.TronAccount).Spender = trcSpender.Address() + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} + +// @BasePath /api/v1 +// +// TronApproveContractV1 godoc +// +// @Summary Approve USDT Spender Contract +// @Schemes +// @Description Approve USDT spender for contract +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TRXAprroveContractRequest true "Account Info" +// @Success 200 {object} TRXApproveUSDTResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/approve-usdt-contract [post] +func TronApproveUsdtContractV1(c *gin.Context) { + var req TRXAprroveContractRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := trc.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := trc.TronBalance(account.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.TRXRaw < 1000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough TRX to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(req.ConractAddress, approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.TronAccount).Spender = req.ConractAddress + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/tron_v1_balance.go b/pkg/rest/tron_v1_balance.go new file mode 100644 index 0000000..acffce5 --- /dev/null +++ b/pkg/rest/tron_v1_balance.go @@ -0,0 +1,85 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type TronAddressBalanceRequest struct { + Address string `json:"address"` +} + +type TronAccountBalanceRequest struct { + AccountID string `json:"account_id"` +} + +// @BasePath /api/v1 +// +// AddressBalance godoc +// +// @Summary Get address balance +// @Schemes +// @Description Get address balance +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TronAddressBalanceRequest true "Account Info" +// @Success 200 {object} trc.TronAccountBalance +// @Failure 400 {object} ErrorResponse +// @Router /tron/address-balance [post] +func TronAddressBalanceV1(c *gin.Context) { + var req TronAddressBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := trc.TronBalance(req.Address) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, balance) +} + +// @BasePath /api/v1 +// +// AddressBalance godoc +// +// @Summary Get address balance +// @Schemes +// @Description Get address balance +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TronAccountBalanceRequest true "Account Info" +// @Success 200 {object} trc.TronAccountBalance +// @Failure 400 {object} ErrorResponse +// @Router /tron/account-balance [post] +func TronAccountBalanceV1(c *gin.Context) { + var req TronAccountBalanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := trc.TronBalance(storedAccount.GetAddress()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, balance) +} diff --git a/pkg/rest/tron_v1_custodial.go b/pkg/rest/tron_v1_custodial.go new file mode 100644 index 0000000..26361e2 --- /dev/null +++ b/pkg/rest/tron_v1_custodial.go @@ -0,0 +1,84 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type TRXAprroveCustodyRequest struct { + Address string `json:"address"` +} + +type TRXApproveCustodyResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// TronApproveUsdtV1 godoc +// +// @Summary Approve USDT Spender +// @Schemes +// @Description Approve USDT spender for account +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TRXAprroveCustodyRequest true "Account Info" +// @Success 200 {object} TRXApproveCustodyResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/approve-usdt-custody [post] +func TronApproveUsdtCustodyV1(c *gin.Context) { + var req TRXAprroveCustodyRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccountByAddress(req.Address, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // if storedAccount.GetSpender() != "" { + // c.JSON(http.StatusBadRequest, gin.H{"error": "Spender already approved"}) + // return + // } + + trcSpender, _ := trc.GetSpender() + approveAmount := "1000000000000" + + hdIndex := storedAccount.GetHDIndex() + account, err := trc.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + balance, err := trc.TronBalance(account.Address()) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + if balance.TRXRaw < 1000000 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough TRX to pay for transaction"}) + return + } + + txid, err := account.ApproveUSDTSpender(trcSpender.Address(), approveAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount.(*store.TronAccount).Spender = trcSpender.Address() + store.UpdateAccount(storedAccount) + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/tron_v1_recharge.go b/pkg/rest/tron_v1_recharge.go new file mode 100644 index 0000000..4ec5b7b --- /dev/null +++ b/pkg/rest/tron_v1_recharge.go @@ -0,0 +1,63 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type RechargeTronRequest struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` +} + +type RechargeTronResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +// @BasePath /api/v1 +// +// TronRechargeTrxBySpenderV1 godoc +// +// @Summary Add TRX from spender account +// @Schemes +// @Description Add TRX funds from the spender account +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body RechargeTronRequest true "Account Info" +// @Success 200 {object} RechargeTronResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/recharge-trx-by-spender [post] +func TronRechargeTrxBySpenderV1(c *gin.Context) { + var req RechargeTronRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spender, err := trc.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spender.WithdrawTRX(storedAccount.GetAddress(), req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/rest/tron_v1_transaction.go b/pkg/rest/tron_v1_transaction.go new file mode 100644 index 0000000..c350bc5 --- /dev/null +++ b/pkg/rest/tron_v1_transaction.go @@ -0,0 +1,66 @@ +package rest + +import ( + "net/http" + + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type TronTransactionInfoRequest struct { + TxID string `json:"tx_id"` +} + +type TronTransactionInfoResponse struct { + Id string `json:"id"` + Status string `json:"status"` +} + +// @BasePath /api/v1 +// +// AddressBalance godoc +// +// @Summary Get transaction info +// @Schemes +// @Description Get transaction info +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body TronTransactionInfoRequest true "Tx Info" +// @Success 200 {object} TronTransactionInfoResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/tx-info [post] +func TronTransactionInfoV1(c *gin.Context) { + var req TronTransactionInfoRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tx, err := trc.GetTransactionByHash(req.TxID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + status := "pending" + ret := 9999 + if tx != nil { + ret = int(tx.Ret[0].ContractRet.Number()) + } + + if ret != 1 && ret != 9999 { + status = "failed" + } else { + status = "success" + } + + res := TronTransactionInfoResponse{ + Id: req.TxID, + Status: status, + } + + c.JSON(http.StatusOK, res) +} diff --git a/pkg/rest/tron_v1_withdraw.go b/pkg/rest/tron_v1_withdraw.go new file mode 100644 index 0000000..cbe8c01 --- /dev/null +++ b/pkg/rest/tron_v1_withdraw.go @@ -0,0 +1,224 @@ +package rest + +import ( + "net/http" + + store "custodial/pkg/store" + trc "custodial/pkg/tron" + + gin "github.com/gin-gonic/gin" +) + +type WithdrawTronRequest struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` + To string `json:"to"` +} + +type WithdrawTronResponse struct { + Status string `json:"status"` + TXID string `json:"txid"` +} + +type WidtdrawUSDTByContract struct { + AccountID string `json:"account_id"` + Amount string `json:"amount"` + To string `json:"to"` + ComissionAmount string `json:"comission_amount"` + ContractAddress string `json:"contract_address"` +} + +// @BasePath /api/v1 +// +// TronWithdrawUsdtByContractV1 godoc +// +// @Summary Withdraw USDT by commision contract +// @Schemes +// @Description Withdraw usdt funds from tron account using approved comission contract +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WidtdrawUSDTByContract true "Account Info" +// @Success 200 {object} WithdrawTronResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/withdraw-usdt-by-contract [post] +func TronWithdrawUsdtByContractV1(c *gin.Context) { + var req WidtdrawUSDTByContract + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spenderAccount, err := trc.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spenderAccount.WithdrawUSDTByCommissionContract(req.ContractAddress, storedAccount.GetAddress(), req.To, req.Amount, req.ComissionAmount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "txid": txid, + "status": "ok", + }) +} + +// @BasePath /api/v1 +// +// TronWithdrawUsdtV1 godoc +// +// @Summary Withdraw USDT by spender +// @Schemes +// @Description Withdraw usdt funds from tron account using approved spender +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawUSDTBySpenderRequest true "Account Info" +// @Success 200 {object} WithdrawSpenderResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/withdraw-usdt-by-spender [post] +func TronWithdrawUsdtBySpenderV1(c *gin.Context) { + var req WithdrawUSDTBySpenderRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + spenderAccount, err := trc.GetSpender() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := spenderAccount.WithdrawUSDTBySpender(storedAccount.GetAddress(), req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + comission := "" + if req.ComissionAmount != "" && req.ComissionAmount != "0" { + txidComission, err := spenderAccount.WithdrawUSDTBySpender( + storedAccount.GetAddress(), + spenderAccount.Address(), + req.ComissionAmount, + ) + if err != nil { + comission = err.Error() + } else { + comission = txidComission + } + } + + c.JSON(http.StatusOK, gin.H{ + "txid_withdrawal": txid, + "txid_commission": comission, + "status": "ok", + }) +} + +// @BasePath /api/v1 +// +// TronWithdrawUsdtV1 godoc +// +// @Summary Withdraw USDT +// @Schemes +// @Description Withdraw usdt funds from tron account +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawTronRequest true "Account Info" +// @Success 200 {object} WithdrawTronResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/withdraw-usdt [post] +func TronWithdrawUsdtV1(c *gin.Context) { + var req WithdrawTronRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hdIndex := storedAccount.GetHDIndex() + account, err := trc.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := account.WithdrawUSDT(req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} + +// @BasePath /api/v1 +// +// TronWithdrawUsdtV1 godoc +// +// @Summary Withdraw TRX +// @Schemes +// @Description Withdraw TRON funds from tron account +// @Tags Tron +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param message body WithdrawTronRequest true "Account Info" +// @Success 200 {object} WithdrawTronResponse +// @Failure 400 {object} ErrorResponse +// @Router /tron/withdraw-trx [post] +func TronWithdrawTrxV1(c *gin.Context) { + var req WithdrawTronRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + storedAccount, err := store.GetAccount(req.AccountID, store.Tron) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hdIndex := storedAccount.GetHDIndex() + account, err := trc.DeriveAccount(hdIndex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + txid, err := account.WithdrawTRX(req.To, req.Amount) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"txid": txid, "status": "ok"}) +} diff --git a/pkg/store/account.go b/pkg/store/account.go new file mode 100644 index 0000000..ee6a064 --- /dev/null +++ b/pkg/store/account.go @@ -0,0 +1,173 @@ +package store + +import ( + "fmt" + + "gorm.io/gorm" +) + +type NetworkType string + +const ( + Tron NetworkType = "tron" + Ethereum NetworkType = "ethereum" +) + +type Account interface { + GetAccountID() string + GetHDIndex() int + GetAddress() string + GetSpender() string + GetLabel() string +} + +func CreateAccount(a Account) error { + db, err := Init() + if err != nil { + return err + } + if a.GetAccountID() == "" { + return fmt.Errorf("account_id is required") + } + return db.Create(a).Error +} + +func UpdateAccount(a Account) error { + db, err := Init() + if err != nil { + return err + } + return db.Save(a).Error +} + +func GetAccount(accountId string, network NetworkType) (Account, error) { + db, err := Init() + if err != nil { + return nil, err + } + var tx *gorm.DB = db.Where("account_id = ?", accountId) + var acc Account + switch network { + case Tron: + var account TronAccount + acc = &account + err = tx.First(&account).Error + case Ethereum: + var account EthereumAccount + acc = &account + err = tx.First(&account).Error + default: + return nil, fmt.Errorf("unknown network type: %s", network) + } + if err != nil { + return nil, err + } + return acc, nil +} + +func GetAccountByAddress(address string, network NetworkType) (Account, error) { + db, err := Init() + if err != nil { + return nil, err + } + var tx *gorm.DB = db.Where("address = ?", address) + var acc Account + switch network { + case Tron: + var account TronAccount + acc = &account + err = tx.First(&account).Error + case Ethereum: + var account EthereumAccount + acc = &account + err = tx.First(&account).Error + default: + return nil, fmt.Errorf("unknown network type: %s", network) + } + if err != nil { + return nil, err + } + return acc, nil +} + +func CountAccounts(network NetworkType) (int64, error) { + db, err := Init() + if err != nil { + return 0, err + } + var count int64 + switch network { + case Tron: + var account TronAccount + err = db.Model(&account).Count(&count).Error + case Ethereum: + var account EthereumAccount + err = db.Model(&account).Count(&count).Error + default: + return 0, fmt.Errorf("unknown network type: %s", network) + } + if err != nil { + return 0, err + } + return count, nil +} + +func LastAccount(network NetworkType) (Account, error) { + db, err := Init() + if err != nil { + return nil, err + } + var tx *gorm.DB + var acc Account + + switch network { + case Tron: + var account TronAccount + acc = &account + tx = db.Last(&account) + case Ethereum: + var account EthereumAccount + acc = &account + tx = db.Last(&account) + default: + return nil, fmt.Errorf("unknown network type: %s", network) + } + if err := tx.Error; err != nil { + return nil, err + } + return acc, nil +} + +func ListAccounts(network NetworkType, offset int, limit int) ([]Account, error) { + db, err := Init() + if err != nil { + return nil, err + } + var tx *gorm.DB + switch network { + case Tron: + var accounts []TronAccount = []TronAccount{} + tx = db.Offset(offset).Limit(limit).Find(&accounts) + if err := tx.Error; err != nil { + return nil, err + } + var convertedAccounts []Account = make([]Account, len(accounts)) + for i := range accounts { + convertedAccounts[i] = Account(&accounts[i]) + } + return convertedAccounts, nil + case Ethereum: + var accounts []EthereumAccount = []EthereumAccount{} + tx = db.Offset(offset).Limit(limit).Find(&accounts) + if err := tx.Error; err != nil { + return nil, err + } + var convertedAccounts []Account = make([]Account, len(accounts)) + for i := range accounts { + convertedAccounts[i] = Account(&accounts[i]) + } + return convertedAccounts, nil + default: + return nil, fmt.Errorf("unknown network type: %s", network) + } +} diff --git a/pkg/store/db.go b/pkg/store/db.go new file mode 100644 index 0000000..3d38258 --- /dev/null +++ b/pkg/store/db.go @@ -0,0 +1,60 @@ +package store + +import ( + "fmt" + "os" + "sync" + + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +const DB_TYPE string = "DB_TYPE" +const DB_CONNECTION_SETTINGS string = "DB_CONNECTION_SETTINGS" +const MAX_ACCOUNT_INDEX int = 4294967295 + +var db *gorm.DB +var once sync.Once + +func Init() (*gorm.DB, error) { + + dbType, isDbTypePresent := os.LookupEnv(DB_TYPE) + dbConn, isDbConnPresent := os.LookupEnv(DB_CONNECTION_SETTINGS) + if !isDbConnPresent || !isDbTypePresent { + return nil, fmt.Errorf("missing environment variables: %s=%s, %s=%s", DB_CONNECTION_SETTINGS, dbConn, DB_TYPE, dbType) + } + + if db != nil { + return db, nil + } + fmt.Println("dbType: ", dbType) + fmt.Println("dbConn: ", dbConn) + var _err error + once.Do(func() { + var dialect gorm.Dialector + switch dbType { + case "sqlite": + dialect = sqlite.Open(dbConn) + case "postgres": + dialect = postgres.Open(dbConn) + default: + _err = fmt.Errorf("unknown database type: %s", dbType) + return + } + _db, err := gorm.Open(dialect, &gorm.Config{}) + if err != nil { + _err = err + return + } + + db = _db + db.AutoMigrate(&TronAccount{}) + db.AutoMigrate(&EthereumAccount{}) + db.AutoMigrate(&Webhook{}) + }) + if _err != nil { + return nil, _err + } + return db, nil +} diff --git a/pkg/store/eth_account.go b/pkg/store/eth_account.go new file mode 100644 index 0000000..bcc38eb --- /dev/null +++ b/pkg/store/eth_account.go @@ -0,0 +1,32 @@ +package store + +import "gorm.io/gorm" + +type EthereumAccount struct { + gorm.Model + AccountID string `gorm:"unique" json:"account_id"` + Label string `json:"label"` + Address string `gorm:"unique" json:"address"` + HDIndex int `gorm:"unique" json:"hd_index"` + Spender string `json:"spender"` +} + +func (a *EthereumAccount) GetAccountID() string { + return a.AccountID +} + +func (a *EthereumAccount) GetHDIndex() int { + return a.HDIndex +} + +func (a *EthereumAccount) GetAddress() string { + return a.Address +} + +func (a *EthereumAccount) GetSpender() string { + return a.Spender +} + +func (a *EthereumAccount) GetLabel() string { + return a.Label +} diff --git a/pkg/store/trx_account.go b/pkg/store/trx_account.go new file mode 100644 index 0000000..12e0376 --- /dev/null +++ b/pkg/store/trx_account.go @@ -0,0 +1,32 @@ +package store + +import "gorm.io/gorm" + +type TronAccount struct { + gorm.Model + AccountID string `gorm:"unique" json:"account_id"` + Label string `json:"label"` + Address string `gorm:"unique" json:"address"` + HDIndex int `gorm:"unique" json:"hd_index"` + Spender string `json:"spender"` +} + +func (a *TronAccount) GetAccountID() string { + return a.AccountID +} + +func (a *TronAccount) GetHDIndex() int { + return a.HDIndex +} + +func (a *TronAccount) GetAddress() string { + return a.Address +} + +func (a *TronAccount) GetSpender() string { + return a.Spender +} + +func (a *TronAccount) GetLabel() string { + return a.Label +} diff --git a/pkg/store/webhook.go b/pkg/store/webhook.go new file mode 100644 index 0000000..b5ec0b4 --- /dev/null +++ b/pkg/store/webhook.go @@ -0,0 +1,79 @@ +package store + +import "gorm.io/gorm" + +type Webhook struct { + gorm.Model + AccountID string `json:"account_id"` + Network string `json:"network"` + Type string `json:"type"` + ContractAddress string `json:"contract_address"` + TransactionHash string `json:"transaction_hash"` + From string `json:"from"` + To string `json:"to"` + Value string `json:"value"` + IsPending bool `json:"is_pending"` +} + +func (a *Webhook) GetNetwork() string { + return a.Network +} + +func (a *Webhook) GetType() string { + return a.Type +} + +func (a *Webhook) GetContractAddress() string { + return a.ContractAddress +} + +func (a *Webhook) GetTransactionHash() string { + return a.TransactionHash +} + +func (a *Webhook) GetFrom() string { + return a.From +} + +func (a *Webhook) GetTo() string { + return a.To +} + +func (a *Webhook) GetValue() string { + return a.Value +} + +func CreateWebhook(a Webhook) error { + err := db.Create(&a).Error + return err +} + +func DeleteWebhook(r Webhook) error { + err := db.Exec("DELETE FROM webhooks WHERE id = ?", r.ID).Error + return err +} + +func SetWebhookPending(id uint, is_pending bool) error { + err := db.Model(Webhook{}).Where("id = ?", id).Update("is_pending", is_pending).Error + return err +} + +func InitWebhookStore() error { + err := db.Exec("UPDATE webhooks SET is_pending = false WHERE is_pending = true").Error + return err +} + +func GetLastWebhook() (Webhook, error) { + var webhook Webhook + err := db.Raw(` + UPDATE webhooks SET is_pending = true + WHERE id IN ( + SELECT id FROM webhooks WHERE + is_pending = false + LIMIT 1 + FOR UPDATE SKIP LOCKED + ) + RETURNING * + `).Scan(&webhook).Error + return webhook, err +} diff --git a/pkg/tron/tron.go b/pkg/tron/tron.go new file mode 100644 index 0000000..01097f1 --- /dev/null +++ b/pkg/tron/tron.go @@ -0,0 +1,176 @@ +package tron + +import ( + "fmt" + "os" + "sync" +) + +const CONTRACT_ADDRESS string = "TRC20_USDT_CONTRACT_ADDRESS" +const TRON_GRPC_NODE string = "TRON_GRPC_NODE" + +type TronNode struct { + contractAddress string + grpcNode string + feeLimit int64 +} + +var tronNode *TronNode +var once sync.Once + +func GetTRC20ContractAddress() string { + return tronNode.contractAddress +} + +func (t TronNode) Init() (*TronNode, error) { + + if tronNode != nil { + return tronNode, nil + } + + trc20Contract, isAddressPresent := os.LookupEnv(CONTRACT_ADDRESS) + if !isAddressPresent { + return nil, fmt.Errorf("missing environment variable: %s", CONTRACT_ADDRESS) + } + + grpcNode, isGrpcNodePresent := os.LookupEnv(TRON_GRPC_NODE) + if !isGrpcNodePresent { + return nil, fmt.Errorf("missing environment variable: %s", TRON_GRPC_NODE) + } + + if tronNode == nil { + once.Do(func() { + t.contractAddress = trc20Contract + t.grpcNode = grpcNode + t.feeLimit = 100000000 + tronNode = &t + }) + } + + return tronNode, nil +} + +const COMMON_ABI string = ` +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ { "name": "", "type": "string" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "approve", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ { "name": "", "type": "uint8" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ { "name": "_owner", "type": "address" } ], + "name": "balanceOf", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ { "name": "", "type": "string" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_token", "type": "address" }, + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" }, + { "name": "_camount", "type": "uint256" } + ], + "name": "chargeTransfer", + "outputs": [ { "name": "", "type": "bool" } ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" } + ], + "name": "allowance", + "outputs": [ { "name": "", "type": "uint256" } ], + "payable": false, + "type": "function" + }, + { "inputs": [], "payable": false, "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "_from", "type": "address" }, + { "indexed": true, "name": "_to", "type": "address" }, + { "indexed": false, "name": "_value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "_owner", "type": "address" }, + { "indexed": true, "name": "_spender", "type": "address" }, + { "indexed": false, "name": "_value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + } +] +` diff --git a/pkg/tron/tron_account.go b/pkg/tron/tron_account.go new file mode 100644 index 0000000..3e760ce --- /dev/null +++ b/pkg/tron/tron_account.go @@ -0,0 +1,72 @@ +package tron + +import ( + "fmt" + "strings" + + "custodial/pkg/locker" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/fbsobreira/gotron-sdk/pkg/address" + "github.com/fbsobreira/gotron-sdk/pkg/keys/hd" + "github.com/tyler-smith/go-bip39" +) + +const MAX_ACCOUNT_INDEX int = 4294967295 + +type TronAccount struct { + privateKey *secp256k1.PrivateKey + publicKey *secp256k1.PublicKey + address string +} + +func (t *TronAccount) PrivateKey() *secp256k1.PrivateKey { + return t.privateKey +} + +func (t *TronAccount) PublicKey() *secp256k1.PublicKey { + return t.publicKey +} + +func (t *TronAccount) Address() string { + return t.address +} + +var master [32]byte +var ch [32]byte + +func InitMaster(mnemonic, passphrase string) { + if master != [32]byte{} { + return + } + seed := bip39.NewSeed(strings.TrimSpace(mnemonic), strings.TrimSpace(passphrase)) + master, ch = hd.ComputeMastersFromSeed(seed, []byte("Bitcoin seed")) +} + +func InitMasterFromPrivateSettings() { + mnemonic := locker.GetPrivateEnv("BIP39_MNEMONIC") + passphrase := locker.GetPrivateEnv("PASSPHRASE") + if mnemonic == "" { + panic("BIP39_MNEMONIC is not set") + } + + InitMaster(mnemonic, passphrase) +} + +func DeriveAccount(index int) (*TronAccount, error) { + private, _ := hd.DerivePrivateKeyForPath( + btcec.S256(), + master, + ch, + fmt.Sprintf("44'/195'/0'/0/%d", index), + ) + t := TronAccount{} + t.privateKey, t.publicKey = btcec.PrivKeyFromBytes(private[:]) + t.address = address.PubkeyToAddress(*t.publicKey.ToECDSA()).String() + return &t, nil +} + +func GetSpender() (*TronAccount, error) { + return DeriveAccount(MAX_ACCOUNT_INDEX) +} diff --git a/pkg/tron/tron_approve.go b/pkg/tron/tron_approve.go new file mode 100644 index 0000000..c7fbc93 --- /dev/null +++ b/pkg/tron/tron_approve.go @@ -0,0 +1,42 @@ +package tron + +import ( + "encoding/hex" + "fmt" + "strconv" + + "github.com/fbsobreira/gotron-sdk/pkg/client" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + + +func (ta *TronAccount) ApproveUSDTSpender(spender string, amount string) (string, error) { + amountFloat, _ := strconv.ParseFloat(amount, 64) + holder := ta.Address() + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", err + } + + txe, err := conn.TRC20Approve(holder, spender, tronNode.contractAddress, Float64ToDecimals(amountFloat, 6), tronNode.feeLimit) + if err != nil { + return "", err + } + + tx, err := ta.SignTx(txe.Transaction) + if err != nil { + return "", err + } + + _, err = conn.Broadcast(tx) + if err != nil { + fmt.Println("Broadcast error: ", err) + return "", err + } + + conn.Stop() + return hex.EncodeToString(txe.GetTxid()), nil + +} \ No newline at end of file diff --git a/pkg/tron/tron_balance.go b/pkg/tron/tron_balance.go new file mode 100644 index 0000000..b9769a0 --- /dev/null +++ b/pkg/tron/tron_balance.go @@ -0,0 +1,112 @@ +package tron + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/fbsobreira/gotron-sdk/pkg/address" + "github.com/fbsobreira/gotron-sdk/pkg/client" + "github.com/fbsobreira/gotron-sdk/pkg/proto/api" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type TronAccountBalance struct { + Address string `json:"address"` + USDT string `json:"usdt"` + TRX string `json:"trx"` + USDTRaw int64 `json:"usdtRaw"` + TRXRaw int64 `json:"trxRaw"` + TxFee string `json:"txFee"` +} + +func TronBalance(address string) (*TronAccountBalance, error) { + var accountBalance TronAccountBalance = TronAccountBalance{ + Address: address, + USDT: "0", + TRX: "0", + USDTRaw: 0, + TRXRaw: 0, + TxFee: "0", + } + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + + balance, err := conn.TRC20ContractBalance(address, tronNode.contractAddress) + if err != nil { + return nil, err + } + + decimals, err := conn.TRC20GetDecimals(tronNode.contractAddress) + if err != nil { + return nil, err + } + + fmtUsdtBalance, _ := FormatSum(balance.Int64(), decimals.Int64()) + accountBalance.USDT = fmtUsdtBalance + accountBalance.USDTRaw = balance.Int64() + + account, err := conn.GetAccount(address) + if err != nil { + if err.Error() == "account not found" { + return &accountBalance, nil + } + return nil, err + } + params, _ := conn.Client.GetChainParameters(context.Background(), &api.EmptyMessage{}) + txFee := 0 + for _, el := range params.GetChainParameter() { + if el.Key == "getTotalEnergyTargetLimit" { + txFee += int(el.Value) + } + if el.Key == "getTransactionFee" { + txFee += int(el.Value) + } + } + accountBalance.TxFee, _ = FormatSum(int64(txFee), 6) + fmtTrxBalance, _ := FormatSum(account.Balance, 6) + accountBalance.TRX = fmtTrxBalance + accountBalance.TRXRaw = account.Balance + conn.Stop() + return &accountBalance, nil +} + +func TronAllowance(owner, spender string) (int64, error) { + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return 0, err + } + + data := crypto.Keccak256Hash([]byte("allowance(address,address)")).Bytes()[:4] + ownerAddress, _ := address.Base58ToAddress(owner) + spenderAddress, _ := address.Base58ToAddress(spender) + + req := "" + req += common.Bytes2Hex(data) + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(ownerAddress.Hex())-4:] + ownerAddress.Hex()[4:] + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(spenderAddress.Hex())-4:] + spenderAddress.Hex()[4:] + + res, err := conn.TRC20Call("", tronNode.contractAddress, req, true, 0) + + if err != nil { + fmt.Println("TRC20Call error: ", err) + return 0, err + } + + allowance := new(big.Int) + //fmt.Println("res", res) + if len(res.GetConstantResult()) > 0 { + allowance.SetBytes(res.GetConstantResult()[0]) + } + + conn.Stop() + return allowance.Int64(), nil + +} diff --git a/pkg/tron/tron_block.go b/pkg/tron/tron_block.go new file mode 100644 index 0000000..688cd86 --- /dev/null +++ b/pkg/tron/tron_block.go @@ -0,0 +1,34 @@ +package tron + +import ( + "github.com/fbsobreira/gotron-sdk/pkg/client" + "github.com/fbsobreira/gotron-sdk/pkg/proto/api" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func TronLastBlock() (uint64, error) { + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return 0, err + } + block, err := conn.GetNowBlock() + if err != nil { + return 0, err + } + return uint64(block.BlockHeader.RawData.Number), nil +} + +func TronBlockByNumber(blockNumber uint64) (*api.BlockExtention, error) { + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + block, err := conn.GetBlockByNum(int64(blockNumber)) + if err != nil { + return nil, err + } + return block, nil +} diff --git a/pkg/tron/tron_transaction.go b/pkg/tron/tron_transaction.go new file mode 100644 index 0000000..0fa1986 --- /dev/null +++ b/pkg/tron/tron_transaction.go @@ -0,0 +1,91 @@ +package tron + +import ( + "crypto/sha256" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + ecommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/fbsobreira/gotron-sdk/pkg/client" + "github.com/fbsobreira/gotron-sdk/pkg/common" + "github.com/fbsobreira/gotron-sdk/pkg/proto/core" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" +) + +func (ta *TronAccount) SignTx(tx *core.Transaction) (*core.Transaction, error) { + + rawData, err := proto.Marshal(tx.GetRawData()) + if err != nil { + return nil, err + } + h256h := sha256.New() + h256h.Write(rawData) + hash := h256h.Sum(nil) + + signature, err := crypto.Sign(hash, ta.privateKey.ToECDSA()) + if err != nil { + return nil, err + } + tx.Signature = append(tx.Signature, signature) + + return tx, nil +} + +type TransactionRawData = map[string]interface{} +type TransactionData struct { + From string + To string + Value string +} + +func DecodeTRC20Transfer(data []byte) (tx TransactionData, err error) { + abi, err := abi.JSON(strings.NewReader(COMMON_ABI)) + if err != nil { + return TransactionData{}, err + } + raw := make(TransactionRawData) + if len(data) < 4 { + return TransactionData{}, nil + } + methodSigData := data[:4] + method, err := abi.MethodById(methodSigData) + if err != nil { + return TransactionData{}, err + } + inputsSigData := data[4:] + if err := method.Inputs.UnpackIntoMap(raw, inputsSigData); err != nil { + return TransactionData{}, err + } + tx = TransactionData{} + if raw["_from"] != nil { + tx.From = common.EncodeCheck( + ToTronAddress(raw["_from"].(ecommon.Address).Bytes()), + ) + } + if raw["_to"] != nil { + tx.To = common.EncodeCheck( + ToTronAddress(raw["_to"].(ecommon.Address).Bytes()), + ) + } + if raw["_value"] != nil { + tx.Value = raw["_value"].(*big.Int).String() + } + return tx, nil +} + +func GetTransactionByHash(txHash string) (*core.Transaction, error) { + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + tx, err := conn.GetTransactionByID(txHash) + if err != nil { + return nil, err + } + return tx, nil +} diff --git a/pkg/tron/tron_util.go b/pkg/tron/tron_util.go new file mode 100644 index 0000000..6f78bd5 --- /dev/null +++ b/pkg/tron/tron_util.go @@ -0,0 +1,31 @@ +package tron + +import ( + "fmt" + "math" + "math/big" + "strconv" + + "github.com/fbsobreira/gotron-sdk/pkg/common/decimals" +) + +func FormatSum(amount int64, decimals int64) (string, float64) { + value := float64(amount) / math.Pow(10, float64(decimals)) + return fmt.Sprintf("%.2f", value), value +} + +func FormatSumString(amount string, dec int64) (string, float64) { + floatValue, _ := strconv.ParseFloat(amount, 64) + value := floatValue / math.Pow(10, float64(dec)) + return fmt.Sprintf("%.2f", value), value +} + +func Float64ToDecimals(amount float64, dec int64) *big.Int { + value, _ := decimals.ApplyDecimals(big.NewFloat(amount), dec) + return value +} + +func ToTronAddress(eth []byte) []byte { + prefix := []byte{0x41} + return append(prefix, eth...) +} diff --git a/pkg/tron/tron_withdraw.go b/pkg/tron/tron_withdraw.go new file mode 100644 index 0000000..a0aa7c7 --- /dev/null +++ b/pkg/tron/tron_withdraw.go @@ -0,0 +1,184 @@ +package tron + +import ( + "encoding/hex" + "fmt" + "strconv" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/fbsobreira/gotron-sdk/pkg/address" + "github.com/fbsobreira/gotron-sdk/pkg/client" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +const trc20TransferFromSig = "0x23b872dd" + +func (ta *TronAccount) WithdrawUSDTByCommissionContract(contractAddr string, from string, to string, amount string, camount string) (string, error) { + amountFloat, _ := strconv.ParseFloat(amount, 64) + comissionFloat, _ := strconv.ParseFloat(camount, 64) + + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", err + } + + dec, err := conn.TRC20GetDecimals(tronNode.contractAddress) + if err != nil { + return "", err + } + + metodId := crypto.Keccak256Hash([]byte("chargeTransfer(address,address,address,uint256,uint256)")).Bytes()[:4] + + token, _ := address.Base58ToAddress(tronNode.contractAddress) + fromAddress, _ := address.Base58ToAddress(from) + toAddress, _ := address.Base58ToAddress(to) + + amountToSend := Float64ToDecimals(amountFloat, dec.Int64()) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + + comissionToSend := Float64ToDecimals(comissionFloat, dec.Int64()) + paddedComission := common.LeftPadBytes(comissionToSend.Bytes(), 32) + + req := "" + req += common.Bytes2Hex(metodId) + fmt.Println("req: ", req) + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(token.Hex())-4:] + token.Hex()[4:] + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(fromAddress.Hex())-4:] + fromAddress.Hex()[4:] + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(toAddress.Hex())-4:] + toAddress.Hex()[4:] + req += common.Bytes2Hex(paddedAmount) + req += common.Bytes2Hex(paddedComission) + + txe, err := conn.TRC20Call(ta.Address(), contractAddr, req, false, tronNode.feeLimit) + if err != nil { + fmt.Println("TRC20Call error: ", err) + return "", err + } + + tx, err := ta.SignTx(txe.Transaction) + if err != nil { + return "", err + } + + _, err = conn.Broadcast(tx) + if err != nil { + fmt.Println("Broadcast error: ", err) + return "", err + } + + conn.Stop() + return hex.EncodeToString(txe.GetTxid()), nil +} + +func (ta *TronAccount) WithdrawUSDTBySpender(from string, to string, amount string) (string, error) { + amountFloat, _ := strconv.ParseFloat(amount, 64) + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", err + } + + dec, err := conn.TRC20GetDecimals(tronNode.contractAddress) + if err != nil { + return "", err + } + + + addrA, _ := address.Base58ToAddress(from) + addrB, _ := address.Base58ToAddress(to) + + amountToSend := Float64ToDecimals(amountFloat, dec.Int64()) + paddedAmount := common.LeftPadBytes(amountToSend.Bytes(), 32) + + req := trc20TransferFromSig + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(addrA.Hex())-4:] + addrA.Hex()[4:] + req += "0000000000000000000000000000000000000000000000000000000000000000"[len(addrB.Hex())-4:] + addrB.Hex()[4:] + req += common.Bytes2Hex(paddedAmount) + + txe, err := conn.TRC20Call(ta.Address(), tronNode.contractAddress, req, false, tronNode.feeLimit) + if err != nil { + return "", err + } + + tx, err := ta.SignTx(txe.Transaction) + if err != nil { + return "", err + } + + _, err = conn.Broadcast(tx) + if err != nil { + fmt.Println("Broadcast error: ", err) + return "", err + } + + conn.Stop() + return hex.EncodeToString(txe.GetTxid()), nil +} + + +func (ta *TronAccount) WithdrawUSDT(recipient string, amount string) (string, error) { + amountFloat, _ := strconv.ParseFloat(amount, 64) + sender := ta.Address() + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", err + } + + dec, err := conn.TRC20GetDecimals(tronNode.contractAddress) + if err != nil { + return "", err + } + + amountToSend := Float64ToDecimals(amountFloat, dec.Int64()) + txe, err := conn.TRC20Send(sender, recipient, tronNode.contractAddress, amountToSend, tronNode.feeLimit) + if err != nil { + return "", err + } + + tx, err := ta.SignTx(txe.Transaction) + if err != nil { + return "", err + } + + _, err = conn.Broadcast(tx) + if err != nil { + fmt.Println("Broadcast error: ", err) + return "", err + } + + conn.Stop() + return hex.EncodeToString(txe.GetTxid()), nil +} + +func (ta *TronAccount) WithdrawTRX(recipient string, amount string) (string, error) { + amountFloat, _ := strconv.ParseFloat(amount, 64) + sender := ta.Address() + conn := client.NewGrpcClient(tronNode.grpcNode) + err := conn.Start(grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", err + } + + amountToSend := Float64ToDecimals(amountFloat, 6) + txe, err := conn.Transfer(sender, recipient, amountToSend.Int64()) + if err != nil { + return "", err + } + + tx, err := ta.SignTx(txe.Transaction) + if err != nil { + return "", err + } + + _, err = conn.Broadcast(tx) + if err != nil { + fmt.Println("Broadcast error: ", err) + return "", err + } + + conn.Stop() + return hex.EncodeToString(txe.GetTxid()), nil +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..640939c --- /dev/null +++ b/readme.md @@ -0,0 +1,148 @@ +# Wallet + +Example rest api for ERC20 and TRC20 wallets. +Use it for research, [contact me](https:/l12.xyz) for questions. + +## Prerequsites + +- Golang +- GCC + +### Running local server + +```bash +docker compose up +``` + +- Open + [http://localhost:28080/swagger/index.html](http://localhost:28080/swagger/index.html) + for docs and tests +- Default dev auth token is `qwertyuiop` +- Api base url is `http://localhost:28080/api/v1` +- Api token header is `Authorization` +- All necessary envs are already set for development, check out `dev.env` for + tests. + +### Private environment + +There are parts of the settings that a developer should not have access to, for +example: _private master keys and passphrases_ that are used to initiate +transactions on the blockchain. During debugging, the developers and devops can +generate their own keypairs and use generic passphrases and mnemonics, however +in the production, only the public key is accessible by developers and devops. + +The mnemonics, passphrases and the other critical settings should be encrypted +using the public key. The public key can be shared with developers and devops. +The private keys should be stored securely. The production environment variables +should be hidden. + +Some environment variables are required to be encrypted with a public RSA key: + +```bash +PRIVATE__BIP36_MNEMONIC= +PRIVATE__PASSPHRASE= +``` + +Use the `keypair` in order to generate private keys and +[ENVCrypt](./cmd/envcrypt.html) to encrypt private variables. + +### Evironment variables + +```bash +TRON_GRPC_NODE=grpc.nile.trongrid.io:50051 +TRC20_USDT_CONTRACT_ADDRESS=TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj +ERC20_USDT_CONTRACT_ADDRESS=0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7 +ERC20_USDT_CONTRACT_DECIMALS=8 +ETH_RPC_NODE=https://goerli.infura.io/v3/bf691c4573fd45c7b244e067cc094d8d +DB_TYPE=sqlite +DB_CONNECTION_SETTINGS=dev-database.sqlite +PRIVATE__BIP39_MNEMONIC= +PRIVATE__API_MASTER_KEY= +PRIVATE__PASSPHRASE= +PUBLIC_KEY= +PRIVATE_KEY= +``` + +Database: + +```bash +DB_TYPE=sqlite|postgres +DB_CONNECTION_SETTINGS=# sqlite: path/to/db ; postgre: host=localhost user=gorm password=gorm dbname=custodial port=9920 +``` + +Private variables can be encrypted using [ENVCrypt](./cmd/envcrypt.html): + +```bash +PRIVATE__BIP36_MNEMONIC=BIP36 Mnemonic +PRIVATE__API_MASTER_KEY=API Key +PRIVATE__PASSPHRASE=Mnemonic password (leave empty for Metamask and other) +``` + +Private key: + +``` +PUBLIC_KEY=RSA Base64 +PRIVATE_KEY=RSA Base64 +``` + +- Normally, those variables are used on production server. +- This variables should be hidden on CI and any public settings. + +### Development + +```bash +go mod tidy +make develop +``` + +Open [:8080](http://localhost:8080/swagger/index.html) + +### Dev deployment + +1. Mount storage for the database file +2. Set `DB_CONNECTION_SETTINGS=/mount/storage/dev-database.sqlite` +3. Make sure that `dev.env` in the current directiory +4. Run `build/custodial --env=dev` + +### Production deployment + +##### Simple variant + +1. Generate RSA keys + +```bash +mkdir keys +keypair generate ./keys +``` + +2. Encrypt private variables: + +- Open [ENVCrypt](./cmd/envcrypt.html) +- Set the public key from `keys/public.pem` +- Encrypt vars and set them on CI Settings + +3. Setup a private keystore on your server: You need to store `keys/private.rsa` + and `keys/public.rsa` securely. + +4. Run the production server: + +```bash +PUBLIC_KEY=$(cat /keystore/public.rsa) PRIVATE_KEY=$(cat /keystore/private.rsa) build/custodial --env=production +``` + +The server listens on `:8080` + +##### More secure variant + +3. Embed private key into binary: + +- Edit [pkg/locker/keys.go](./pkg/locker/keys.go) to set it as default values. +- Build binary `make build` + +4. Run the production server: + +```bash +build/custodial --env=production +``` + +The server listens on `:8080`