[init]
This commit is contained in:
commit
80299cc2c4
44
.air.toml
Normal file
44
.air.toml
Normal file
|
@ -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
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.env
|
||||
bin/
|
||||
build/
|
||||
tmp/
|
||||
node_modules/
|
||||
dev-database.sqlite
|
||||
dev.sqlite
|
152
.gitlab-ci.yml
Normal file
152
.gitlab-ci.yml
Normal file
|
@ -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
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
|
@ -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" ]
|
34
Makefile
Normal file
34
Makefile
Normal file
|
@ -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
|
559
cmd/envcrypt.html
Normal file
559
cmd/envcrypt.html
Normal file
|
@ -0,0 +1,559 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ENVCrypt - env encrypt tool</title>
|
||||
<link rel="stylesheet" href="https://cdn.rawgit.com/Chalarangelo/mini.css/v3.0.1/dist/mini-default.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<meta name="description" content="ENVCrypt - env encrypt tool using RSA pubkey in one html file"/>
|
||||
<style>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
background-color: #aaa;
|
||||
}
|
||||
footer {
|
||||
color:#087abe;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
header .logo {
|
||||
font-size: 1.32rem;
|
||||
color: #000;
|
||||
font-weight: 200;
|
||||
}
|
||||
header .spacer {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.app {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 3px;
|
||||
}
|
||||
.env {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.value {
|
||||
position: relative;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.value fieldset {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
.action {
|
||||
position: absolute;
|
||||
right:0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
background: #ddd;
|
||||
}
|
||||
[type="checkbox"].modal + div .card {
|
||||
margin: 0 auto;
|
||||
max-height: unset;
|
||||
overflow: auto;
|
||||
}
|
||||
.add-env {
|
||||
background: transparent;
|
||||
}
|
||||
.add-env input {
|
||||
opacity: 1;
|
||||
}
|
||||
.copy-action {
|
||||
position: absolute;
|
||||
left: -50px;
|
||||
top: -8px;
|
||||
bottom: 0px;
|
||||
height: 42px;
|
||||
}
|
||||
.envcrypted .action, .envcrypted .copy-action {
|
||||
display: none;
|
||||
}
|
||||
.envcrypted:hover .action, .envcrypted:hover .copy-action {
|
||||
display: block;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<script type="module">
|
||||
import { html, Component, render, useState } from 'https://unpkg.com/htm/preact/standalone.module.js';
|
||||
|
||||
function getCurrentDocument() {
|
||||
return localStorage.getItem('currentDocument') || 'default';
|
||||
}
|
||||
|
||||
function setCurrentDocument(name) {
|
||||
localStorage.setItem('currentDocument', name);
|
||||
}
|
||||
|
||||
function documentList() {
|
||||
return JSON.parse(localStorage.getItem('documentList')) || [];
|
||||
}
|
||||
|
||||
function addDocument(name) {
|
||||
const list = documentList();
|
||||
const existing = list.find(item => item == name);
|
||||
if (existing) {
|
||||
Swal.fire('Document already exists');
|
||||
return;
|
||||
}
|
||||
list.push(name);
|
||||
localStorage.setItem('documentList', JSON.stringify(list));
|
||||
}
|
||||
|
||||
function getCurrentDocumentItems() {
|
||||
const currentDocument = getCurrentDocument();
|
||||
return JSON.parse(localStorage.getItem(currentDocument + '_items')) || [];
|
||||
}
|
||||
|
||||
function saveItem(data) {
|
||||
const currentDocument = getCurrentDocument();
|
||||
const items = getCurrentDocumentItems();
|
||||
const existingItem = items.find(item => item.name == data.name);
|
||||
if (existingItem) {
|
||||
Swal.fire('Item already exists');
|
||||
return items;
|
||||
}
|
||||
items.push(data);
|
||||
localStorage.setItem(currentDocument + '_items', JSON.stringify(items));
|
||||
return items;
|
||||
}
|
||||
|
||||
function delItem(data) {
|
||||
const currentDocument = getCurrentDocument();
|
||||
const items = getCurrentDocumentItems();
|
||||
const newItems = items.filter(item => item.name !== data.name);
|
||||
localStorage.setItem(currentDocument + '_items', JSON.stringify(newItems));
|
||||
return newItems;
|
||||
}
|
||||
|
||||
function editItem(data, name) {
|
||||
const currentDocument = getCurrentDocument();
|
||||
const items = getCurrentDocumentItems();
|
||||
const newItems = items.map(item => {
|
||||
if (item.name == data.name) {
|
||||
return {
|
||||
name: name,
|
||||
value: item.value
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
localStorage.setItem(currentDocument + '_items', JSON.stringify(newItems));
|
||||
return newItems;
|
||||
}
|
||||
|
||||
function getPubKey() {
|
||||
const currentDocument = getCurrentDocument();
|
||||
return localStorage.getItem(currentDocument + '_pubkey');
|
||||
}
|
||||
|
||||
function setPubKey(pub) {
|
||||
const currentDocument = getCurrentDocument();
|
||||
localStorage.setItem(currentDocument + '_pubkey', pub);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function delPubKey() {
|
||||
const _confirm = confirm('Are you sure? This action will delete all encrypted data.');
|
||||
if (!_confirm) {
|
||||
return false;
|
||||
}
|
||||
const currentDocument = getCurrentDocument();
|
||||
localStorage.removeItem(currentDocument + '_pubkey');
|
||||
localStorage.removeItem(currentDocument + '_items');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function getSha() {
|
||||
const currentDocument = getCurrentDocument();
|
||||
return localStorage.getItem(currentDocument + '_sha') || 'SHA-512';
|
||||
}
|
||||
|
||||
function setSha(sha) {
|
||||
const currentDocument = getCurrentDocument();
|
||||
localStorage.setItem(currentDocument + '_sha', sha);
|
||||
}
|
||||
|
||||
function delSha() {
|
||||
currentDocument = getCurrentDocument();
|
||||
localStorage.removeItem(currentDocument + '_sha');
|
||||
}
|
||||
|
||||
async function encryptText(text) {
|
||||
const pub = getPubKey();
|
||||
const sha = getSha();
|
||||
const key = await importRsaKey(pub, sha);
|
||||
const encrypted = await encryptDataWithPublicKey(text, key);
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
addItem(data) {
|
||||
const items = saveItem(data);
|
||||
this.setState({ items: items });
|
||||
}
|
||||
delItem(data) {
|
||||
const items = delItem(data);
|
||||
this.setState({ items, });
|
||||
}
|
||||
editItem(data, name) {
|
||||
const items = editItem(data, name);
|
||||
this.setState({ items: items });
|
||||
}
|
||||
render({ page }, {
|
||||
currentDocument = getCurrentDocument(),
|
||||
items = getCurrentDocumentItems(),
|
||||
}) {
|
||||
return html`
|
||||
<div class="app">
|
||||
<${Header} itemsLength=${items.length} isPubKeySet=${getPubKey()}/>
|
||||
<ul style="min-width: 50vw; opacity: .89;">
|
||||
${
|
||||
!items.length ? html`
|
||||
<li class="row value" style="justify-content:center;">
|
||||
<div style="padding:6%;">
|
||||
<p>No records yet. Try to add a new ENV</p>
|
||||
</div>
|
||||
</li>
|
||||
` : ''
|
||||
}
|
||||
${items.map(item => html`
|
||||
<${ShowEnvItem} name=${item.name} value=${item.value} onEdit=${(name) => this.editItem(item, name)} onRemove=${()=> this.delItem(item)} />
|
||||
`)}
|
||||
</ul>
|
||||
<hr />
|
||||
<ul class="add-env">
|
||||
<${AddEnvItem} onAdd="${(data) => this.addItem(data)}"/>
|
||||
</ul>
|
||||
<${Footer} />
|
||||
<${PubKeyModal} />
|
||||
<${RawTextModal} />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const ShowEnvItem = ({onRemove, onEdit, name, value}) => {
|
||||
const onEnvCopy = async (ev) => {
|
||||
ev.preventDefault()
|
||||
const text = `${name}=${value}`
|
||||
await navigator.clipboard.writeText(text);
|
||||
Swal.fire({
|
||||
position: 'bottom-end',
|
||||
icon: 'success',
|
||||
title: 'Copied to clipboard',
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
})
|
||||
}
|
||||
return html`
|
||||
<li class="row value envcrypted">
|
||||
<input type="text" onBlur=${(ev) => onEdit(ev.target.value)} onKeyUp=${(ev) => onEdit(ev.target.value)} class="env col-sm-12 col-md-4" placeholder="Key" value=${name}/>
|
||||
<input type="text" class="env col-sm-12 col-md-8" placeholder="Value" value=${value} style="pointer-events:none;"/>
|
||||
<button class="action" onClick=${onRemove}>
|
||||
<div style="transform: rotate(90deg);">⨉</div>
|
||||
</button>
|
||||
<button class="copy-action" onClick=${onEnvCopy}>
|
||||
💾
|
||||
</button>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
const AddEnvItem = ({onAdd}) => {
|
||||
const onValidate = () => {
|
||||
const envName = document.getElementById('env-name').value;
|
||||
const envValue = document.getElementById('env-value').value;
|
||||
if (!envName || !envValue) {
|
||||
Swal.fire('Please fill env name and value');
|
||||
return [false, false];
|
||||
}
|
||||
return [envName, envValue];
|
||||
}
|
||||
const onSave = async () => {
|
||||
const [envName, envValue] = onValidate();
|
||||
if (!envName || !envValue) {
|
||||
return;
|
||||
}
|
||||
if (!getPubKey()) {
|
||||
Swal.fire('Please set a public key first');
|
||||
return;
|
||||
}
|
||||
onAdd({
|
||||
name: envName,
|
||||
value: await encryptText(envValue),
|
||||
});
|
||||
document.getElementById('env-name').value = '';
|
||||
document.getElementById('env-value').value = '';
|
||||
}
|
||||
return html`
|
||||
<li class="row value" style="background: transparent;">
|
||||
<fieldset>
|
||||
<legend>Add new env</legend>
|
||||
<input type="text" id="env-name" class="env col-sm-12 col-md-4" placeholder="Name" />
|
||||
<input type="text" id="env-value" class="env col-sm-12 col-md-8" placeholder="Value" />
|
||||
<button class="action" style="bottom:8px; right: 6px; top: unset;" onClick=${onSave}>
|
||||
<span class="icon-lock"></span>
|
||||
Encrypt
|
||||
</button>
|
||||
</fieldset>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
const RawTextModal = ({}) => {
|
||||
const onSubmit = async (ev) => {
|
||||
ev.preventDefault();
|
||||
const text = ev.target.text.value;
|
||||
if (!getPubKey()) {
|
||||
Swal.fire('Please set RSA PubKey first');
|
||||
return;
|
||||
}
|
||||
document.getElementById('result').value = await encryptText(text);
|
||||
}
|
||||
|
||||
const onCopy = async (ev) => {
|
||||
ev.preventDefault();
|
||||
const text = document.getElementById('result').value;
|
||||
await navigator.clipboard.writeText(text);
|
||||
Swal.fire({
|
||||
position: 'bottom-end',
|
||||
icon: 'success',
|
||||
title: 'Copied to clipboard',
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
})
|
||||
}
|
||||
|
||||
const onClear = async (ev) => {
|
||||
ev.preventDefault();
|
||||
document.getElementById('text').value = '';
|
||||
document.getElementById('result').value = '';
|
||||
console.log('clear');
|
||||
}
|
||||
return html`
|
||||
<input type="checkbox" id="modal-text" class="modal" />
|
||||
<div role="dialog" aria-labelledby="dialog-title">
|
||||
<div class="card large">
|
||||
<label for="modal-text" class="modal-close"></label>
|
||||
<h3 class="section">Encrypt text</h3>
|
||||
<form onSubmit=${onSubmit}>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<textarea style="min-width: 100%; min-height: 150px;" class="col-sm-12" id="text" name="text" placeholder="Text to encrypt"></textarea>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<textarea style="min-width: 100%; min-height: 150px;" class="col-sm-12" id="result" name="result" placeholder="Result"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div style="display:flex; justify-content: space-between;">
|
||||
<button type="button" onClick=${onClear}>
|
||||
Clear
|
||||
</button>
|
||||
<button type="submit" class="primary">Encrypt</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
const PubKeyModal = ({}) => {
|
||||
const sha = getSha() || 'SHA-512';
|
||||
const pubkey = getPubKey() || '';
|
||||
const onSubmit = (ev) => {
|
||||
ev.preventDefault();
|
||||
const pubkey = ev.target.pubkey.value;
|
||||
const sha = ev.target.sha.value;
|
||||
if (pubkey && sha) {
|
||||
setPubKey(pubkey);
|
||||
setSha(sha);
|
||||
}
|
||||
console.log('submit', pubkey, sha);
|
||||
}
|
||||
const onClear = (ev) => {
|
||||
ev.preventDefault();
|
||||
if (!delPubKey()) {
|
||||
return
|
||||
}
|
||||
delSha();
|
||||
document.getElementById('pubkey').value = '';
|
||||
document.getElementById('sha').value = 'SHA-512';
|
||||
console.log('clear');
|
||||
}
|
||||
const OpenFile = async (ev) => {
|
||||
ev.preventDefault();
|
||||
const file = document.createElement('input');
|
||||
file.type = 'file';
|
||||
file.accept = '.pub,.pem';
|
||||
file.click();
|
||||
|
||||
file.onchange = async (ev) => {
|
||||
const pubkey = await file.files[0].text();
|
||||
document.getElementById('pubkey').value = pubkey;
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<input type="checkbox" id="modal-control" class="modal" />
|
||||
<div role="dialog" aria-labelledby="dialog-title">
|
||||
<div class="card large">
|
||||
<label for="modal-control" class="modal-close"></label>
|
||||
<h3 class="section">Edit RSA PubKey</h3>
|
||||
<form onSubmit=${onSubmit}>
|
||||
<div class="row">
|
||||
<div class="col-sm-12" style="text-align: right;">
|
||||
<textarea style="min-width: 100%; min-height: 150px;" class="col-sm-12" id="pubkey" name="pubkey" placeholder="RSA PubKey">${pubkey}</textarea>
|
||||
<a href="#" onClick=${OpenFile}>Open file</a>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<select id="sha" value="${sha}" name="sha">
|
||||
<option value="SHA-512">SHA-512</option>
|
||||
<option value="SHA-384">SHA-384</option>
|
||||
<option value="SHA-256">SHA-256</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div style="display:flex; justify-content: space-between;">
|
||||
<button type="button" onClick=${onClear}>
|
||||
Clear
|
||||
</button>
|
||||
<button type="submit" class="primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
const Header = ({ itemsLength, isPubKeySet}) => {
|
||||
const [documents, setDocuments] = useState(documentList());
|
||||
const [currentDocument, _setCurrentDocument] = useState(getCurrentDocument());
|
||||
const downloadEnvs = async (ev) => {
|
||||
if (!itemsLength) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
const envs = await getCurrentDocumentItems();
|
||||
let text = '';
|
||||
for (const env of envs) {
|
||||
text += `${env.name}=${env.value}\n`;
|
||||
}
|
||||
const blob = new Blob([text], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${getCurrentDocument()}.env`;
|
||||
a.click();
|
||||
}
|
||||
const onAddDocument = (ev) => {
|
||||
ev.preventDefault();
|
||||
const name = prompt('Enter document name');
|
||||
if (name) {
|
||||
addDocument(name);
|
||||
setDocuments(documentList());
|
||||
setCurrentDocument(name);
|
||||
_setCurrentDocument(name);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
const setDocument = async (ev) => {
|
||||
const name = ev.target.value;
|
||||
if (name === '$add') {
|
||||
onAddDocument(ev);
|
||||
} else {
|
||||
setCurrentDocument(name);
|
||||
_setCurrentDocument(name);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<header>
|
||||
<a href="#" onClick=${downloadEnvs} role="button" disabled=${itemsLength <= 0}>
|
||||
💾
|
||||
Save
|
||||
</a>
|
||||
<label class="button" for="modal-text">
|
||||
<span class="icon-edit"></span>
|
||||
Encrypt text
|
||||
</label>
|
||||
<label class="button" for="modal-control">
|
||||
<span class="icon-lock"></span>
|
||||
${isPubKeySet ? '' : 'Set' } PUB Key
|
||||
</label>
|
||||
<span class="spacer">
|
||||
</span>
|
||||
<select value=${currentDocument} onChange=${setDocument}>
|
||||
<option value="default">Environment: default</option>
|
||||
${documents.map((doc) => html`
|
||||
<option value="${doc}">Environment: ${doc}</option>
|
||||
`)}
|
||||
<option value="$add">± Add Environment</option>
|
||||
</select>
|
||||
</header>
|
||||
`
|
||||
}
|
||||
|
||||
const Footer = () => html`
|
||||
<footer style="display: flex; justify-content: space-between;">
|
||||
<span>[ENV]Crypt | <a href="https://github.com/nesterow">@nesterow</a></span>
|
||||
<small>RSA-OAEP, SHA-512, SHA-256</small>
|
||||
</footer>
|
||||
`
|
||||
|
||||
render(html`<${App} page="All" />`, document.body);
|
||||
</script>
|
||||
<!-- UTILS -->
|
||||
<script>
|
||||
function str2ab(str) {
|
||||
const buf = new ArrayBuffer(str.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
function encryptDataWithPublicKey(data, key) {
|
||||
data = str2ab(data);
|
||||
return window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
},
|
||||
key,
|
||||
data
|
||||
).then((encrypted) => {
|
||||
return btoa(String.fromCharCode.apply(null, new Uint8Array(encrypted)));
|
||||
});
|
||||
}
|
||||
function importRsaKey(pem, sha) {
|
||||
const pemHeader = "-----BEGIN PUBLIC KEY-----";
|
||||
const pemFooter = "-----END PUBLIC KEY-----";
|
||||
const pemContents = pem.substring(
|
||||
pemHeader.length,
|
||||
pem.length - pemFooter.length - 1,
|
||||
);
|
||||
const binaryDerString = window.atob(pemContents);
|
||||
const binaryDer = str2ab(binaryDerString);
|
||||
return window.crypto.subtle.importKey(
|
||||
"spki",
|
||||
binaryDer,
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
hash: sha || "SHA-512",
|
||||
},
|
||||
true,
|
||||
["encrypt"],
|
||||
).catch((err) => console.log(err));
|
||||
}
|
||||
</script>
|
||||
</html>
|
51
cmd/keypair/keypair_test.go
Normal file
51
cmd/keypair/keypair_test.go
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
96
cmd/keypair/main.go
Normal file
96
cmd/keypair/main.go
Normal file
|
@ -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 <dir>")
|
||||
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 <dir> - Generate a new keypair and save it to <dir>")
|
||||
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()
|
||||
}
|
||||
}
|
58
cmd/prebuild/main.go
Normal file
58
cmd/prebuild/main.go
Normal file
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
16
dev.env
Normal file
16
dev.env
Normal file
|
@ -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=
|
44
docker-compose.yaml
Normal file
44
docker-compose.yaml
Normal file
|
@ -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:
|
2069
docs/docs.go
Normal file
2069
docs/docs.go
Normal file
File diff suppressed because it is too large
Load diff
2040
docs/swagger.json
Normal file
2040
docs/swagger.json
Normal file
File diff suppressed because it is too large
Load diff
1279
docs/swagger.yaml
Normal file
1279
docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load diff
104
go.mod
Normal file
104
go.mod
Normal file
|
@ -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
|
||||
)
|
373
go.sum
Normal file
373
go.sum
Normal file
|
@ -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=
|
28
keys/private.pem
Normal file
28
keys/private.pem
Normal file
|
@ -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-----
|
1
keys/private.rsa
Normal file
1
keys/private.rsa
Normal file
|
@ -0,0 +1 @@
|
|||
TUFbZs63+FV+9KLgmoHgT1VK9YMuGUn///uqfrbtxx4MywtWpkPAS/QVTnSlwERxdQR9hIXR8xMAavWJ5Ix/ZLizLXVhhX4w1w7FE4SKmMewgUIK7NwlWWgDKIGlRTQlCOiOal6g3H4Mp6ndQGwNK8zo3NVlhekAKX57v8zrAvK6gWL/P3lXAwZVlSKxGREHZb7bX2mVWv4tSXFEKp1p2pzlV/qWCTcn67mSygotkk2ExB9Knm9d0V2l+klzbQHdx38RhwFvfBetf84w8a5Gb+Fv4fwvi6Um9cbcGtW1IvX0cb24YG2MfJKVKEAI9hhfGDJZZ99hzI2nljY2qu374QoKpXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww==
|
9
keys/public.pem
Normal file
9
keys/public.pem
Normal file
|
@ -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-----
|
1
keys/public.rsa
Normal file
1
keys/public.rsa
Normal file
|
@ -0,0 +1 @@
|
|||
pXMH2SKSlqhXOC5a5N3PDi873rksucf2UAm5hAkjoyRSdY15ZXWwqNF7WE4zvVzBBS+nd+exo8l8guK+plNRzsdDumdsDkCZ1arfx1RCCAQ6GQRvaziE0riDcUq37iSo0uxnBSUnKOXkgjigCKThQ5f4/NLDNfHlfFD0ZB+cjNyhSsKqUelob+Eb4Mqjxx+QdmV6PRCJbdr3hxwsoqVWHVlOhDhYNy/wGjoW8p4YVLtQmNOjhopMjKpZbOmvimdSgqeQnmssC4FN+PmOtxow9nc1uckKWZt3WifVNMa2iwI43sqs8AZX08i8OqgWPa/lj5UinuW7ETLW88lb+RJ2Ww==
|
29
main.go
Normal file
29
main.go
Normal file
|
@ -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")
|
||||
}
|
93
pkg/crypto/keypair.go
Normal file
93
pkg/crypto/keypair.go
Normal file
|
@ -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
|
||||
}
|
50
pkg/crypto/rsa.go
Normal file
50
pkg/crypto/rsa.go
Normal file
|
@ -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
|
||||
}
|
32
pkg/crypto/utils.go
Normal file
32
pkg/crypto/utils.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
183
pkg/eth/eth.go
Normal file
183
pkg/eth/eth.go
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
`
|
116
pkg/eth/eth_account.go
Normal file
116
pkg/eth/eth_account.go
Normal file
|
@ -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)
|
||||
}
|
64
pkg/eth/eth_approve.go
Normal file
64
pkg/eth/eth_approve.go
Normal file
|
@ -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
|
||||
}
|
107
pkg/eth/eth_balance.go
Normal file
107
pkg/eth/eth_balance.go
Normal file
|
@ -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
|
||||
}
|
37
pkg/eth/eth_block.go
Normal file
37
pkg/eth/eth_block.go
Normal file
|
@ -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
|
||||
}
|
138
pkg/eth/eth_transaction.go
Normal file
138
pkg/eth/eth_transaction.go
Normal file
|
@ -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
|
||||
}
|
17
pkg/eth/eth_util.go
Normal file
17
pkg/eth/eth_util.go
Normal file
|
@ -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
|
||||
}
|
230
pkg/eth/eth_withdraw.go
Normal file
230
pkg/eth/eth_withdraw.go
Normal file
|
@ -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
|
||||
}
|
4
pkg/locker/keys.go
Normal file
4
pkg/locker/keys.go
Normal file
|
@ -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=="
|
38
pkg/locker/settings.go
Normal file
38
pkg/locker/settings.go
Normal file
|
@ -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()
|
||||
}
|
39
pkg/locker/types.go
Normal file
39
pkg/locker/types.go
Normal file
|
@ -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
|
||||
}
|
18
pkg/rest/account_v1.go
Normal file
18
pkg/rest/account_v1.go
Normal file
|
@ -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)
|
||||
}
|
153
pkg/rest/account_v1_balance.go
Normal file
153
pkg/rest/account_v1_balance.go
Normal file
|
@ -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,
|
||||
})
|
||||
}
|
41
pkg/rest/account_v1_count.go
Normal file
41
pkg/rest/account_v1_count.go
Normal file
|
@ -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,
|
||||
})
|
||||
}
|
123
pkg/rest/account_v1_create.go
Normal file
123
pkg/rest/account_v1_create.go
Normal file
|
@ -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(),
|
||||
})
|
||||
}
|
61
pkg/rest/account_v1_list.go
Normal file
61
pkg/rest/account_v1_list.go
Normal file
|
@ -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)
|
||||
}
|
62
pkg/rest/account_v1_spender.go
Normal file
62
pkg/rest/account_v1_spender.go
Normal file
|
@ -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,
|
||||
})
|
||||
}
|
21
pkg/rest/auth_middleware.go
Normal file
21
pkg/rest/auth_middleware.go
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
18
pkg/rest/echo_v1.go
Normal file
18
pkg/rest/echo_v1.go
Normal file
|
@ -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)
|
||||
}
|
33
pkg/rest/eth_v1.go
Normal file
33
pkg/rest/eth_v1.go
Normal file
|
@ -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)
|
||||
}
|
76
pkg/rest/eth_v1_account.go
Normal file
76
pkg/rest/eth_v1_account.go
Normal file
|
@ -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()})
|
||||
}
|
148
pkg/rest/eth_v1_approve.go
Normal file
148
pkg/rest/eth_v1_approve.go
Normal file
|
@ -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"})
|
||||
}
|
85
pkg/rest/eth_v1_balance.go
Normal file
85
pkg/rest/eth_v1_balance.go
Normal file
|
@ -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)
|
||||
}
|
84
pkg/rest/eth_v1_custodial.go
Normal file
84
pkg/rest/eth_v1_custodial.go
Normal file
|
@ -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"})
|
||||
}
|
63
pkg/rest/eth_v1_recharge.go
Normal file
63
pkg/rest/eth_v1_recharge.go
Normal file
|
@ -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"})
|
||||
}
|
62
pkg/rest/eth_v1_transaction.go
Normal file
62
pkg/rest/eth_v1_transaction.go
Normal file
|
@ -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)
|
||||
}
|
230
pkg/rest/eth_v1_withdraw.go
Normal file
230
pkg/rest/eth_v1_withdraw.go
Normal file
|
@ -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"})
|
||||
}
|
144
pkg/rest/export_v1.go
Normal file
144
pkg/rest/export_v1.go
Normal file
|
@ -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)
|
||||
}
|
37
pkg/rest/routes.go
Normal file
37
pkg/rest/routes.go
Normal file
|
@ -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
|
||||
}
|
30
pkg/rest/sync_middleware.go
Normal file
30
pkg/rest/sync_middleware.go
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
40
pkg/rest/tron_v1.go
Normal file
40
pkg/rest/tron_v1.go
Normal file
|
@ -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)
|
||||
|
||||
}
|
76
pkg/rest/tron_v1_account.go
Normal file
76
pkg/rest/tron_v1_account.go
Normal file
|
@ -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()})
|
||||
}
|
148
pkg/rest/tron_v1_approve.go
Normal file
148
pkg/rest/tron_v1_approve.go
Normal file
|
@ -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"})
|
||||
}
|
85
pkg/rest/tron_v1_balance.go
Normal file
85
pkg/rest/tron_v1_balance.go
Normal file
|
@ -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)
|
||||
}
|
84
pkg/rest/tron_v1_custodial.go
Normal file
84
pkg/rest/tron_v1_custodial.go
Normal file
|
@ -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"})
|
||||
}
|
63
pkg/rest/tron_v1_recharge.go
Normal file
63
pkg/rest/tron_v1_recharge.go
Normal file
|
@ -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"})
|
||||
}
|
66
pkg/rest/tron_v1_transaction.go
Normal file
66
pkg/rest/tron_v1_transaction.go
Normal file
|
@ -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)
|
||||
}
|
224
pkg/rest/tron_v1_withdraw.go
Normal file
224
pkg/rest/tron_v1_withdraw.go
Normal file
|
@ -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"})
|
||||
}
|
173
pkg/store/account.go
Normal file
173
pkg/store/account.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
60
pkg/store/db.go
Normal file
60
pkg/store/db.go
Normal file
|
@ -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
|
||||
}
|
32
pkg/store/eth_account.go
Normal file
32
pkg/store/eth_account.go
Normal file
|
@ -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
|
||||
}
|
32
pkg/store/trx_account.go
Normal file
32
pkg/store/trx_account.go
Normal file
|
@ -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
|
||||
}
|
79
pkg/store/webhook.go
Normal file
79
pkg/store/webhook.go
Normal file
|
@ -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
|
||||
}
|
176
pkg/tron/tron.go
Normal file
176
pkg/tron/tron.go
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
`
|
72
pkg/tron/tron_account.go
Normal file
72
pkg/tron/tron_account.go
Normal file
|
@ -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)
|
||||
}
|
42
pkg/tron/tron_approve.go
Normal file
42
pkg/tron/tron_approve.go
Normal file
|
@ -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
|
||||
|
||||
}
|
112
pkg/tron/tron_balance.go
Normal file
112
pkg/tron/tron_balance.go
Normal file
|
@ -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
|
||||
|
||||
}
|
34
pkg/tron/tron_block.go
Normal file
34
pkg/tron/tron_block.go
Normal file
|
@ -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
|
||||
}
|
91
pkg/tron/tron_transaction.go
Normal file
91
pkg/tron/tron_transaction.go
Normal file
|
@ -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
|
||||
}
|
31
pkg/tron/tron_util.go
Normal file
31
pkg/tron/tron_util.go
Normal file
|
@ -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...)
|
||||
}
|
184
pkg/tron/tron_withdraw.go
Normal file
184
pkg/tron/tron_withdraw.go
Normal file
|
@ -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
|
||||
}
|
148
readme.md
Normal file
148
readme.md
Normal file
|
@ -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`
|
Loading…
Reference in a new issue