mirror of
https://github.com/alexgo-io/brc20-indexer-contracts.git
synced 2026-01-12 22:21:46 +08:00
feat: dev-stack & bootstrap (#12)
* feat: dev-stack & bootstrap Signed-off-by: bestmike007 <i@bestmike007.com> * chore: contract codegen Signed-off-by: bestmike007 <i@bestmike007.com> * chore: add depends_on Signed-off-by: bestmike007 <i@bestmike007.com> --------- Signed-off-by: bestmike007 <i@bestmike007.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.cache
|
.cache
|
||||||
node_modules
|
node_modules
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
/bootstrap/config.json
|
||||||
|
|||||||
@@ -31,5 +31,4 @@ epoch = 2.1
|
|||||||
path = 'contracts/indexer.clar'
|
path = 'contracts/indexer.clar'
|
||||||
clarity_version = 2
|
clarity_version = 2
|
||||||
epoch = 2.1
|
epoch = 2.1
|
||||||
|
depends_on = ["clarity-bitcoin"]
|
||||||
|
|
||||||
|
|||||||
10
bootstrap/constants.ts
Normal file
10
bootstrap/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import config from './config.json';
|
||||||
|
|
||||||
|
export const DEPLOYER_ACCOUNT_ADDRESS = () => config.DEPLOYER_ACCOUNT_ADDRESS;
|
||||||
|
export const DEPLOYER_ACCOUNT_SECRETKEY = () =>
|
||||||
|
config.DEPLOYER_ACCOUNT_SECRETKEY;
|
||||||
|
export const STACKS_API_URL = () =>
|
||||||
|
process.env.STACKS_API_URL || config.STACKS_API_URL;
|
||||||
|
export const STACKS_PUPPET_URL = () =>
|
||||||
|
process.env.STACKS_PUPPET_URL || config.STACKS_PUPPET_URL;
|
||||||
|
export const USER_ACCOUNTS = () => config.USER_ACCOUNTS;
|
||||||
7
bootstrap/contracts/contractNames.ts
Normal file
7
bootstrap/contracts/contractNames.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type contracts from './contracts.json';
|
||||||
|
|
||||||
|
export const Contracts: Array<keyof typeof contracts> = [
|
||||||
|
'utils',
|
||||||
|
'clarity-bitcoin',
|
||||||
|
'indexer',
|
||||||
|
];
|
||||||
17
bootstrap/contracts/contracts.json
Normal file
17
bootstrap/contracts/contracts.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"clarity-bitcoin": {
|
||||||
|
"path": "contracts/clarity-bitcoin.clar",
|
||||||
|
"clarity_version": 2,
|
||||||
|
"epoch": 2.1
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"path": "contracts/utils.clar",
|
||||||
|
"clarity_version": 2,
|
||||||
|
"epoch": 2.1
|
||||||
|
},
|
||||||
|
"indexer": {
|
||||||
|
"path": "contracts/indexer.clar",
|
||||||
|
"clarity_version": 2,
|
||||||
|
"epoch": 2.1
|
||||||
|
}
|
||||||
|
}
|
||||||
12
bootstrap/contracts/generateContractList.ts
Normal file
12
bootstrap/contracts/generateContractList.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import toml from '@iarna/toml';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const clarinetConfig = toml.parse(
|
||||||
|
fs.readFileSync(path.resolve(__dirname, '../..', 'Clarinet.toml'), 'utf8'),
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(__dirname, './contracts.json'),
|
||||||
|
JSON.stringify(clarinetConfig.contracts, null, 2) + '\n',
|
||||||
|
);
|
||||||
14
bootstrap/contracts/generateContracts.ts
Normal file
14
bootstrap/contracts/generateContracts.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { generateContracts } from 'clarity-codegen/lib/generate';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { DEPLOYER_ACCOUNT_ADDRESS, STACKS_API_URL } from '../constants';
|
||||||
|
import { Contracts } from './contractNames';
|
||||||
|
|
||||||
|
(async function main() {
|
||||||
|
await generateContracts(
|
||||||
|
STACKS_API_URL(),
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS(),
|
||||||
|
Contracts,
|
||||||
|
path.resolve(__dirname, './generated/'),
|
||||||
|
'Brc20Indexer',
|
||||||
|
);
|
||||||
|
})().catch(console.error);
|
||||||
399
bootstrap/contracts/generated/contract_clarity-bitcoin.ts
Normal file
399
bootstrap/contracts/generated/contract_clarity-bitcoin.ts
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
defineContract,
|
||||||
|
numberT,
|
||||||
|
bufferT,
|
||||||
|
responseSimpleT,
|
||||||
|
booleanT,
|
||||||
|
optionalT,
|
||||||
|
tupleT,
|
||||||
|
listT
|
||||||
|
} from "clarity-codegen"
|
||||||
|
|
||||||
|
export const clarityBitcoin = defineContract({
|
||||||
|
"clarity-bitcoin": {
|
||||||
|
'mock-add-burnchain-block-header-hash': {
|
||||||
|
input: [
|
||||||
|
{ name: 'burn-height', type: numberT },
|
||||||
|
{ name: 'hash', type: bufferT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'get-bc-h-hash': {
|
||||||
|
input: [ { name: 'bh', type: numberT } ],
|
||||||
|
output: optionalT(bufferT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-reversed-segwit-txid': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-reversed-txid': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-segwit-txid': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-txid': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'inner-merkle-proof-verify': {
|
||||||
|
input: [
|
||||||
|
{ name: 'ctr', type: numberT },
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
type: tupleT({
|
||||||
|
'cur-hash': bufferT,
|
||||||
|
path: numberT,
|
||||||
|
'proof-hashes': listT(bufferT, ),
|
||||||
|
'root-hash': bufferT,
|
||||||
|
'tree-depth': numberT,
|
||||||
|
verified: booleanT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: tupleT({
|
||||||
|
'cur-hash': bufferT,
|
||||||
|
path: numberT,
|
||||||
|
'proof-hashes': listT(bufferT, ),
|
||||||
|
'root-hash': bufferT,
|
||||||
|
'tree-depth': numberT,
|
||||||
|
verified: booleanT
|
||||||
|
}, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'inner-reverse': {
|
||||||
|
input: [
|
||||||
|
{ name: 'target-index', type: numberT },
|
||||||
|
{ name: 'hash-input', type: bufferT }
|
||||||
|
],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'is-bit-set': {
|
||||||
|
input: [ { name: 'val', type: numberT }, { name: 'bit', type: numberT } ],
|
||||||
|
output: booleanT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'parse-block-header': {
|
||||||
|
input: [ { name: 'headerbuff', type: bufferT } ],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
'merkle-root': bufferT,
|
||||||
|
nbits: numberT,
|
||||||
|
nonce: numberT,
|
||||||
|
parent: bufferT,
|
||||||
|
timestamp: numberT,
|
||||||
|
version: numberT
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'parse-tx': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ins: listT(tupleT({
|
||||||
|
outpoint: tupleT({ hash: bufferT, index: numberT }, ),
|
||||||
|
scriptSig: bufferT,
|
||||||
|
sequence: numberT
|
||||||
|
}, ), ),
|
||||||
|
locktime: numberT,
|
||||||
|
outs: listT(tupleT({ scriptPubKey: bufferT, value: numberT }, ), ),
|
||||||
|
version: numberT
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'parse-wtx': {
|
||||||
|
input: [ { name: 'tx', type: bufferT } ],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ins: listT(tupleT({
|
||||||
|
outpoint: tupleT({ hash: bufferT, index: numberT }, ),
|
||||||
|
scriptSig: bufferT,
|
||||||
|
sequence: numberT
|
||||||
|
}, ), ),
|
||||||
|
locktime: numberT,
|
||||||
|
outs: listT(tupleT({ scriptPubKey: bufferT, value: numberT }, ), ),
|
||||||
|
'segwit-marker': numberT,
|
||||||
|
'segwit-version': numberT,
|
||||||
|
version: numberT,
|
||||||
|
witnesses: listT(listT(bufferT, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-hashslice': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'old-ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
hashslice: bufferT
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-next-element': {
|
||||||
|
input: [
|
||||||
|
{ name: 'ignored', type: booleanT },
|
||||||
|
{
|
||||||
|
name: 'state-res',
|
||||||
|
type: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
elements: listT(bufferT, )
|
||||||
|
}, ), )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
elements: listT(bufferT, )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-next-txin': {
|
||||||
|
input: [
|
||||||
|
{ name: 'ignored', type: booleanT },
|
||||||
|
{
|
||||||
|
name: 'state-res',
|
||||||
|
type: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
remaining: numberT,
|
||||||
|
txins: listT(tupleT({
|
||||||
|
outpoint: tupleT({ hash: bufferT, index: numberT }, ),
|
||||||
|
scriptSig: bufferT,
|
||||||
|
sequence: numberT
|
||||||
|
}, ), )
|
||||||
|
}, ), )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
remaining: numberT,
|
||||||
|
txins: listT(tupleT({
|
||||||
|
outpoint: tupleT({ hash: bufferT, index: numberT }, ),
|
||||||
|
scriptSig: bufferT,
|
||||||
|
sequence: numberT
|
||||||
|
}, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-next-txout': {
|
||||||
|
input: [
|
||||||
|
{ name: 'ignored', type: booleanT },
|
||||||
|
{
|
||||||
|
name: 'state-res',
|
||||||
|
type: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
txouts: listT(tupleT({ scriptPubKey: bufferT, value: numberT }, ), )
|
||||||
|
}, ), )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
txouts: listT(tupleT({ scriptPubKey: bufferT, value: numberT }, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-next-witness': {
|
||||||
|
input: [
|
||||||
|
{ name: 'ignored', type: booleanT },
|
||||||
|
{
|
||||||
|
name: 'state-res',
|
||||||
|
type: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
witnesses: listT(listT(bufferT, ), )
|
||||||
|
}, ), )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
witnesses: listT(listT(bufferT, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-txins': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
remaining: numberT,
|
||||||
|
txins: listT(tupleT({
|
||||||
|
outpoint: tupleT({ hash: bufferT, index: numberT }, ),
|
||||||
|
scriptSig: bufferT,
|
||||||
|
sequence: numberT
|
||||||
|
}, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-txouts': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
txouts: listT(tupleT({ scriptPubKey: bufferT, value: numberT }, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-uint16': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ ctx: tupleT({ index: numberT, txbuff: bufferT }, ), uint16: numberT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-uint32': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ ctx: tupleT({ index: numberT, txbuff: bufferT }, ), uint32: numberT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-uint64': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ ctx: tupleT({ index: numberT, txbuff: bufferT }, ), uint64: numberT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-uint8': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ ctx: tupleT({ index: numberT, txbuff: bufferT }, ), uint8: numberT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-varint': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ ctx: tupleT({ index: numberT, txbuff: bufferT }, ), varint: numberT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-varslice': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'old-ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
varslice: bufferT
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'read-witnesses': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'ctx',
|
||||||
|
type: tupleT({ index: numberT, txbuff: bufferT }, )
|
||||||
|
},
|
||||||
|
{ name: 'num-txins', type: numberT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({
|
||||||
|
ctx: tupleT({ index: numberT, txbuff: bufferT }, ),
|
||||||
|
witnesses: listT(listT(bufferT, ), )
|
||||||
|
}, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'reverse-buff32': {
|
||||||
|
input: [ { name: 'input', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'verify-block-header': {
|
||||||
|
input: [
|
||||||
|
{ name: 'headerbuff', type: bufferT },
|
||||||
|
{ name: 'expected-block-height', type: numberT }
|
||||||
|
],
|
||||||
|
output: booleanT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'verify-merkle-proof': {
|
||||||
|
input: [
|
||||||
|
{ name: 'reversed-txid', type: bufferT },
|
||||||
|
{ name: 'merkle-root', type: bufferT },
|
||||||
|
{
|
||||||
|
name: 'proof',
|
||||||
|
type: tupleT({
|
||||||
|
hashes: listT(bufferT, ),
|
||||||
|
'tree-depth': numberT,
|
||||||
|
'tx-index': numberT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'was-segwit-tx-mined?': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'block',
|
||||||
|
type: tupleT({ header: bufferT, height: numberT }, )
|
||||||
|
},
|
||||||
|
{ name: 'tx', type: bufferT },
|
||||||
|
{
|
||||||
|
name: 'proof',
|
||||||
|
type: tupleT({
|
||||||
|
hashes: listT(bufferT, ),
|
||||||
|
'tree-depth': numberT,
|
||||||
|
'tx-index': numberT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'was-tx-mined?': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'block',
|
||||||
|
type: tupleT({ header: bufferT, height: numberT }, )
|
||||||
|
},
|
||||||
|
{ name: 'tx', type: bufferT },
|
||||||
|
{
|
||||||
|
name: 'proof',
|
||||||
|
type: tupleT({
|
||||||
|
hashes: listT(bufferT, ),
|
||||||
|
'tree-depth': numberT,
|
||||||
|
'tx-index': numberT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'mock-burnchain-header-hashes': { input: numberT, output: optionalT(bufferT, ), mode: 'mapEntry' }
|
||||||
|
}
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
|
||||||
193
bootstrap/contracts/generated/contract_indexer.ts
Normal file
193
bootstrap/contracts/generated/contract_indexer.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
defineContract,
|
||||||
|
bufferT,
|
||||||
|
principalT,
|
||||||
|
responseSimpleT,
|
||||||
|
numberT,
|
||||||
|
booleanT,
|
||||||
|
listT,
|
||||||
|
tupleT,
|
||||||
|
stringT,
|
||||||
|
optionalT,
|
||||||
|
noneT
|
||||||
|
} from "clarity-codegen"
|
||||||
|
|
||||||
|
export const indexer = defineContract({
|
||||||
|
"indexer": {
|
||||||
|
'add-validator': {
|
||||||
|
input: [
|
||||||
|
{ name: 'validator-pubkey', type: bufferT },
|
||||||
|
{ name: 'validator', type: principalT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(numberT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'approve-relayer': {
|
||||||
|
input: [
|
||||||
|
{ name: 'relayer', type: principalT },
|
||||||
|
{ name: 'approved', type: booleanT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'index-tx-many': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'tx-many',
|
||||||
|
type: listT(tupleT({
|
||||||
|
block: tupleT({ header: bufferT, height: numberT }, ),
|
||||||
|
proof: tupleT({
|
||||||
|
hashes: listT(bufferT, ),
|
||||||
|
'tree-depth': numberT,
|
||||||
|
'tx-index': numberT
|
||||||
|
}, ),
|
||||||
|
'signature-packs': listT(tupleT({ signature: bufferT, signer: principalT, 'tx-hash': bufferT }, ), ),
|
||||||
|
tx: tupleT({
|
||||||
|
amt: numberT,
|
||||||
|
'bitcoin-tx': bufferT,
|
||||||
|
from: bufferT,
|
||||||
|
'from-bal': numberT,
|
||||||
|
output: numberT,
|
||||||
|
tick: stringT,
|
||||||
|
to: bufferT,
|
||||||
|
'to-bal': numberT
|
||||||
|
}, )
|
||||||
|
}, ), )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'remove-validator': {
|
||||||
|
input: [ { name: 'validator', type: principalT } ],
|
||||||
|
output: responseSimpleT(numberT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'set-contract-owner': {
|
||||||
|
input: [ { name: 'owner', type: principalT } ],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'set-paused': {
|
||||||
|
input: [ { name: 'paused', type: booleanT } ],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'set-required-validators': {
|
||||||
|
input: [ { name: 'new-required-validators', type: numberT } ],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'set-user-balance': {
|
||||||
|
input: [
|
||||||
|
{ name: 'user', type: bufferT },
|
||||||
|
{ name: 'tick', type: stringT },
|
||||||
|
{ name: 'amt', type: numberT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'public'
|
||||||
|
},
|
||||||
|
'get-bitcoin-tx-indexed-or-fail': {
|
||||||
|
input: [
|
||||||
|
{ name: 'bitcoin-tx', type: bufferT },
|
||||||
|
{ name: 'output', type: numberT }
|
||||||
|
],
|
||||||
|
output: responseSimpleT(tupleT({ amt: numberT, from: bufferT, tick: stringT, to: bufferT }, ), ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-contract-owner': { input: [], output: principalT, mode: 'readonly' },
|
||||||
|
'get-paused': { input: [], output: booleanT, mode: 'readonly' },
|
||||||
|
'get-required-validators': { input: [], output: numberT, mode: 'readonly' },
|
||||||
|
'get-user-balance-or-default': {
|
||||||
|
input: [
|
||||||
|
{ name: 'user', type: bufferT },
|
||||||
|
{ name: 'tick', type: stringT }
|
||||||
|
],
|
||||||
|
output: numberT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'get-validator-or-fail': {
|
||||||
|
input: [ { name: 'validator', type: principalT } ],
|
||||||
|
output: responseSimpleT(bufferT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'hash-tx': {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'tx',
|
||||||
|
type: tupleT({
|
||||||
|
amt: numberT,
|
||||||
|
'bitcoin-tx': bufferT,
|
||||||
|
from: bufferT,
|
||||||
|
'from-bal': numberT,
|
||||||
|
output: numberT,
|
||||||
|
tick: stringT,
|
||||||
|
to: bufferT,
|
||||||
|
'to-bal': numberT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'validate-tx': {
|
||||||
|
input: [
|
||||||
|
{ name: 'tx-hash', type: bufferT },
|
||||||
|
{
|
||||||
|
name: 'signature-pack',
|
||||||
|
type: tupleT({ signature: bufferT, signer: principalT, 'tx-hash': bufferT }, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'verify-mined': {
|
||||||
|
input: [
|
||||||
|
{ name: 'tx', type: bufferT },
|
||||||
|
{
|
||||||
|
name: 'block',
|
||||||
|
type: tupleT({ header: bufferT, height: numberT }, )
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'proof',
|
||||||
|
type: tupleT({
|
||||||
|
hashes: listT(bufferT, ),
|
||||||
|
'tree-depth': numberT,
|
||||||
|
'tx-index': numberT
|
||||||
|
}, )
|
||||||
|
}
|
||||||
|
],
|
||||||
|
output: responseSimpleT(booleanT, ),
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'approved-relayers': {
|
||||||
|
input: principalT,
|
||||||
|
output: optionalT(booleanT, ),
|
||||||
|
mode: 'mapEntry'
|
||||||
|
},
|
||||||
|
'bitcoin-tx-indexed': {
|
||||||
|
input: tupleT({ output: numberT, 'tx-hash': bufferT }, ),
|
||||||
|
output: optionalT(tupleT({ amt: numberT, from: bufferT, tick: stringT, to: bufferT }, ), ),
|
||||||
|
mode: 'mapEntry'
|
||||||
|
},
|
||||||
|
'tx-validated-by': {
|
||||||
|
input: tupleT({ 'tx-hash': bufferT, validator: principalT }, ),
|
||||||
|
output: optionalT(booleanT, ),
|
||||||
|
mode: 'mapEntry'
|
||||||
|
},
|
||||||
|
'user-balance': {
|
||||||
|
input: tupleT({ tick: stringT, user: bufferT }, ),
|
||||||
|
output: optionalT(numberT, ),
|
||||||
|
mode: 'mapEntry'
|
||||||
|
},
|
||||||
|
validators: { input: principalT, output: optionalT(bufferT, ), mode: 'mapEntry' },
|
||||||
|
'contract-owner': { input: noneT, output: principalT, mode: 'variable' },
|
||||||
|
'is-paused': { input: noneT, output: booleanT, mode: 'variable' },
|
||||||
|
'required-validators': { input: noneT, output: numberT, mode: 'variable' },
|
||||||
|
'tx-hash-to-iter': { input: noneT, output: bufferT, mode: 'variable' },
|
||||||
|
'validator-count': { input: noneT, output: numberT, mode: 'variable' }
|
||||||
|
}
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
|
||||||
60
bootstrap/contracts/generated/contract_utils.ts
Normal file
60
bootstrap/contracts/generated/contract_utils.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
defineContract,
|
||||||
|
bufferT,
|
||||||
|
numberT,
|
||||||
|
booleanT,
|
||||||
|
stringAsciiT
|
||||||
|
} from "clarity-codegen"
|
||||||
|
|
||||||
|
export const utils = defineContract({
|
||||||
|
"utils": {
|
||||||
|
'byte-to-uint': {
|
||||||
|
input: [ { name: 'byte', type: bufferT } ],
|
||||||
|
output: numberT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'serialize-bool': {
|
||||||
|
input: [ { name: 'value', type: booleanT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'serialize-buff': {
|
||||||
|
input: [ { name: 'value', type: bufferT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'serialize-string': {
|
||||||
|
input: [ { name: 'value', type: stringAsciiT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'serialize-uint': {
|
||||||
|
input: [ { name: 'value', type: numberT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'string-ascii-to-buff': {
|
||||||
|
input: [ { name: 'str', type: stringAsciiT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'uint-to-byte': {
|
||||||
|
input: [ { name: 'n', type: numberT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'uint128-to-buff-be': {
|
||||||
|
input: [ { name: 'n', type: numberT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
},
|
||||||
|
'uint32-to-buff-be': {
|
||||||
|
input: [ { name: 'n', type: numberT } ],
|
||||||
|
output: bufferT,
|
||||||
|
mode: 'readonly'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
|
||||||
12
bootstrap/contracts/generated/contracts_Brc20Indexer.ts
Normal file
12
bootstrap/contracts/generated/contracts_Brc20Indexer.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineContract } from "clarity-codegen";
|
||||||
|
import { utils } from "./contract_utils"
|
||||||
|
import { clarityBitcoin } from "./contract_clarity-bitcoin"
|
||||||
|
import { indexer } from "./contract_indexer"
|
||||||
|
|
||||||
|
export const Brc20IndexerContracts = defineContract({
|
||||||
|
...utils,
|
||||||
|
...clarityBitcoin,
|
||||||
|
...indexer
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
13
bootstrap/contracts/index.ts
Normal file
13
bootstrap/contracts/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { DEPLOYER_ACCOUNT_ADDRESS } from '../constants';
|
||||||
|
import { Brc20IndexerContracts } from './generated/contracts_Brc20Indexer';
|
||||||
|
|
||||||
|
export type Contracts = typeof Brc20IndexerContracts;
|
||||||
|
export type ContractName = keyof Contracts;
|
||||||
|
|
||||||
|
export function contractName<T extends ContractName>(name: T): T {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function principal<T extends ContractName>(contract: T) {
|
||||||
|
return `${DEPLOYER_ACCOUNT_ADDRESS()}.${String(contract)}`;
|
||||||
|
}
|
||||||
27
bootstrap/contracts/operation.d.ts
vendored
Normal file
27
bootstrap/contracts/operation.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ClarityValue } from '@stacks/transactions';
|
||||||
|
|
||||||
|
export type Operation =
|
||||||
|
| Operation.DeployContract
|
||||||
|
| Operation.PublicCall
|
||||||
|
| Operation.TransferSTX;
|
||||||
|
|
||||||
|
export namespace Operation {
|
||||||
|
export type TransferSTX = {
|
||||||
|
type: 'transfer';
|
||||||
|
amount: number;
|
||||||
|
address: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeployContract = {
|
||||||
|
type: 'deploy';
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PublicCall = {
|
||||||
|
type: 'publicCall';
|
||||||
|
contract: string;
|
||||||
|
function: string;
|
||||||
|
args: ClarityValue[];
|
||||||
|
};
|
||||||
|
}
|
||||||
35
bootstrap/contracts/operationFactory.ts
Normal file
35
bootstrap/contracts/operationFactory.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
OpenCallFunctionDescriptor,
|
||||||
|
ParameterObjOfDescriptor,
|
||||||
|
} from 'clarity-codegen';
|
||||||
|
import { Brc20IndexerContracts } from './generated/contracts_Brc20Indexer';
|
||||||
|
import { Operation } from './operation';
|
||||||
|
|
||||||
|
export type Contracts = typeof Brc20IndexerContracts;
|
||||||
|
export type ContractName = keyof Contracts;
|
||||||
|
|
||||||
|
export const callPublic = <
|
||||||
|
T extends ContractName,
|
||||||
|
F extends keyof Contracts[T],
|
||||||
|
>(
|
||||||
|
contractOrType: T,
|
||||||
|
functionName: F,
|
||||||
|
args: Contracts[T][F] extends OpenCallFunctionDescriptor
|
||||||
|
? ParameterObjOfDescriptor<Contracts[T][F]>
|
||||||
|
: never,
|
||||||
|
): Operation.PublicCall => {
|
||||||
|
const descriptor = Brc20IndexerContracts[contractOrType][
|
||||||
|
functionName
|
||||||
|
] as any as OpenCallFunctionDescriptor;
|
||||||
|
return {
|
||||||
|
type: 'publicCall',
|
||||||
|
contract: contractOrType as string,
|
||||||
|
function: functionName as string,
|
||||||
|
args: descriptor.input.map(a => a.type.encode(args[a.name])),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transferStxTo = (
|
||||||
|
address: string,
|
||||||
|
amount: number,
|
||||||
|
): Operation.TransferSTX => ({ amount, address, type: 'transfer' });
|
||||||
31
bootstrap/deploy.ts
Normal file
31
bootstrap/deploy.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import {
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS,
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY,
|
||||||
|
STACKS_API_URL,
|
||||||
|
} from './constants';
|
||||||
|
import { Contracts } from './contracts/contractNames';
|
||||||
|
import { deployContracts } from './setup/deployContracts';
|
||||||
|
import { sleep } from './utils';
|
||||||
|
import { getAccountInfo, processOperations } from './utils/processOperations';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await getAccountInfo(DEPLOYER_ACCOUNT_ADDRESS());
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
`waiting for connecting stacks-node-api: ${STACKS_API_URL()}`,
|
||||||
|
);
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`starting deploy: ${STACKS_API_URL()}`);
|
||||||
|
await processOperations(
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS(),
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY(),
|
||||||
|
10 * 1e6,
|
||||||
|
)(deployContracts(Contracts, path.resolve(__dirname, '..')));
|
||||||
|
})();
|
||||||
28
bootstrap/faucet.ts
Normal file
28
bootstrap/faucet.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS,
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY,
|
||||||
|
} from './constants';
|
||||||
|
import { transferStxTo } from './contracts/operationFactory';
|
||||||
|
import { processOperations } from './utils/processOperations';
|
||||||
|
|
||||||
|
const processAsDeployer = processOperations(
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS(),
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY(),
|
||||||
|
1e6,
|
||||||
|
);
|
||||||
|
|
||||||
|
async function faucet() {
|
||||||
|
const recipient = process.argv[2];
|
||||||
|
if (recipient == null) {
|
||||||
|
console.log(`Usage: yarn faucet <address>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!recipient.startsWith('ST') && !recipient.startsWith('SP')) {
|
||||||
|
console.log(`Invalid stacks address: ${recipient}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await processAsDeployer([transferStxTo(recipient, 100e6)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
faucet().catch(console.error);
|
||||||
34
bootstrap/setup.ts
Normal file
34
bootstrap/setup.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import got from 'got-cjs';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
import {
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS,
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY,
|
||||||
|
USER_ACCOUNTS,
|
||||||
|
} from './constants';
|
||||||
|
import { transferStxTo } from './contracts/operationFactory';
|
||||||
|
import { processOperations } from './utils/processOperations';
|
||||||
|
|
||||||
|
const processAsDeployer = processOperations(
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS(),
|
||||||
|
DEPLOYER_ACCOUNT_SECRETKEY(),
|
||||||
|
1e6,
|
||||||
|
);
|
||||||
|
|
||||||
|
async function setup() {
|
||||||
|
const list: { Address: string }[] = await got
|
||||||
|
.get(
|
||||||
|
'https://still-wave-a807-production.reily.workers.dev/v1/table/474f84ab0c8444ef84feae17dee513e8',
|
||||||
|
)
|
||||||
|
.json();
|
||||||
|
const addresses = list.flatMap(({ Address }) => Address.split(','));
|
||||||
|
|
||||||
|
await processAsDeployer([
|
||||||
|
...uniq([...USER_ACCOUNTS().map(u => u.address), ...addresses]).flatMap(
|
||||||
|
address => [transferStxTo(address, 100e6)],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
.catch(console.error)
|
||||||
|
.then(() => process.exit());
|
||||||
60
bootstrap/setup/deployContracts.ts
Normal file
60
bootstrap/setup/deployContracts.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import toml from '@iarna/toml';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
import path from 'path';
|
||||||
|
import { Operation } from '../contracts/operation';
|
||||||
|
|
||||||
|
type DeployContractTarget = {
|
||||||
|
contractName: string;
|
||||||
|
contractPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Contracts = {
|
||||||
|
[key: string]: {
|
||||||
|
path: string;
|
||||||
|
depends_on: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapContractsToDeployTarget = (
|
||||||
|
contractNames: string[],
|
||||||
|
{ clarinetPath }: { clarinetPath: string },
|
||||||
|
): DeployContractTarget[] => {
|
||||||
|
const clarinetConfig = toml.parse(
|
||||||
|
fs.readFileSync(path.resolve(clarinetPath, 'Clarinet.toml'), 'utf8'),
|
||||||
|
);
|
||||||
|
const contracts = clarinetConfig.contracts as Contracts;
|
||||||
|
function findDeps(name: string): string[] {
|
||||||
|
if (!contracts[name]) {
|
||||||
|
throw new Error(`Could not find contract ${name}`);
|
||||||
|
}
|
||||||
|
const contract = contracts[name].depends_on ?? [];
|
||||||
|
return [...contract.flatMap(findDeps), name];
|
||||||
|
}
|
||||||
|
const sortedContractNames = uniq(contractNames.flatMap(findDeps));
|
||||||
|
return sortedContractNames.map(contractName => {
|
||||||
|
return {
|
||||||
|
contractName,
|
||||||
|
contractPath: path.resolve(clarinetPath, contracts[contractName].path),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function deployContracts(
|
||||||
|
contracts: string[],
|
||||||
|
clarinetPath: string,
|
||||||
|
): Operation.DeployContract[] {
|
||||||
|
const result = mapContractsToDeployTarget(contracts, {
|
||||||
|
clarinetPath,
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`Found ${result.length} deploy targets: ${result
|
||||||
|
.map(r => r.contractName)
|
||||||
|
.join(', ')}`,
|
||||||
|
);
|
||||||
|
return result.map(r => ({
|
||||||
|
type: 'deploy',
|
||||||
|
path: r.contractPath,
|
||||||
|
name: r.contractName,
|
||||||
|
}));
|
||||||
|
}
|
||||||
3
bootstrap/utils/buffer.ts
Normal file
3
bootstrap/utils/buffer.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function fromUint8Array(arr: Uint8Array): Buffer {
|
||||||
|
return Buffer.from(arr, arr.byteOffset, arr.byteLength);
|
||||||
|
}
|
||||||
33
bootstrap/utils/index.ts
Normal file
33
bootstrap/utils/index.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { CoreNodeInfoResponse } from '@stacks/stacks-blockchain-api-types';
|
||||||
|
import { STACKS_API_URL } from '../constants';
|
||||||
|
|
||||||
|
export function assertNever(x: never): never {
|
||||||
|
throw new Error('Unexpected object: ' + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assert(condition: unknown, message: string): asserts condition {
|
||||||
|
if (!condition) throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentInfo(): Promise<CoreNodeInfoResponse> {
|
||||||
|
return fetch(`${STACKS_API_URL()}/v2/info`).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotNull<T>(input: T | undefined | null): input is T {
|
||||||
|
return input != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(r => setTimeout(r, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function repeatForever(fn: () => Promise<void>, interval: number) {
|
||||||
|
// noinspection InfiniteLoopJS
|
||||||
|
while (true) {
|
||||||
|
await fn().catch(e => console.error(e));
|
||||||
|
await sleep(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function random<T>(array: T[]): T {
|
||||||
|
return array[Math.floor(Math.random() * array.length)];
|
||||||
|
}
|
||||||
289
bootstrap/utils/processOperations.ts
Normal file
289
bootstrap/utils/processOperations.ts
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import { StacksMocknet } from '@stacks/network';
|
||||||
|
import {
|
||||||
|
AccountDataResponse,
|
||||||
|
AddressTransactionsListResponse,
|
||||||
|
Transaction,
|
||||||
|
} from '@stacks/stacks-blockchain-api-types';
|
||||||
|
import {
|
||||||
|
AnchorMode,
|
||||||
|
broadcastTransaction,
|
||||||
|
estimateContractFunctionCall,
|
||||||
|
makeContractCall,
|
||||||
|
makeContractDeploy,
|
||||||
|
makeSTXTokenTransfer,
|
||||||
|
PostConditionMode,
|
||||||
|
} from '@stacks/transactions';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { assertNever, sleep } from '.';
|
||||||
|
import {
|
||||||
|
DEPLOYER_ACCOUNT_ADDRESS,
|
||||||
|
STACKS_API_URL,
|
||||||
|
STACKS_PUPPET_URL,
|
||||||
|
} from '../constants';
|
||||||
|
import { Operation } from '../contracts/operation';
|
||||||
|
|
||||||
|
function jsonReplacer(this: any, key: string) {
|
||||||
|
const v = this[key];
|
||||||
|
if (typeof v === 'bigint') return String(v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const processOperations =
|
||||||
|
(address: string, senderKey: string, fee: number = 2 * 1e6) =>
|
||||||
|
async (operations: Operation[]) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const ts = () => `${start}+${Date.now() - start}`;
|
||||||
|
console.log(`Submitting ${operations.length} operations`);
|
||||||
|
let startingNonce = (await getAccountInfo(address)).nonce;
|
||||||
|
console.log(`[${ts()}] starting nonce: ${startingNonce}`);
|
||||||
|
if (operations.length === 0) return startingNonce;
|
||||||
|
let serverNonce = startingNonce;
|
||||||
|
let nonce = serverNonce;
|
||||||
|
|
||||||
|
const puppetUrl = STACKS_PUPPET_URL() ?? '';
|
||||||
|
|
||||||
|
operations = operations.slice();
|
||||||
|
const nonceToOperation = new Map<number, Operation>();
|
||||||
|
let operation: undefined | Operation;
|
||||||
|
while ((operation = operations.shift())) {
|
||||||
|
while (nonce > serverNonce + 25) {
|
||||||
|
if (puppetUrl.length > 0) {
|
||||||
|
await fetch(`${puppetUrl}/kick`, { method: 'POST' });
|
||||||
|
await sleep(30);
|
||||||
|
} else {
|
||||||
|
await sleep(3 * 1000);
|
||||||
|
}
|
||||||
|
serverNonce = (await getAccountInfo(address)).nonce;
|
||||||
|
}
|
||||||
|
console.log(`[${ts()}] processing #${nonce - startingNonce}`);
|
||||||
|
try {
|
||||||
|
nonceToOperation.set(nonce, operation);
|
||||||
|
switch (operation.type) {
|
||||||
|
case 'publicCall':
|
||||||
|
await publicCall(operation, { senderKey, nonce, fee });
|
||||||
|
break;
|
||||||
|
case 'deploy':
|
||||||
|
await deployContract(operation, { senderKey, nonce, fee });
|
||||||
|
break;
|
||||||
|
case 'transfer':
|
||||||
|
await transferSTX(operation, { senderKey, nonce, fee });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assertNever(operation);
|
||||||
|
}
|
||||||
|
nonce++;
|
||||||
|
} catch (e) {
|
||||||
|
if ((e as Error).message.includes('ContractAlreadyExists')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(`[${ts()}] operation failed:`, operation, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (nonce !== serverNonce) {
|
||||||
|
if (puppetUrl.length > 0) {
|
||||||
|
await fetch(`${puppetUrl}/kick`, { method: 'POST' });
|
||||||
|
await sleep(100);
|
||||||
|
} else {
|
||||||
|
await sleep(3 * 1000);
|
||||||
|
}
|
||||||
|
serverNonce = (await getAccountInfo(address)).nonce;
|
||||||
|
}
|
||||||
|
if (nonce > startingNonce) {
|
||||||
|
const txs = await getTransaction(address, startingNonce);
|
||||||
|
const errTxs = txs.filter(tx => tx.tx_status !== 'success');
|
||||||
|
if (errTxs.length) {
|
||||||
|
throw new Error(
|
||||||
|
`[${ts()}] ${errTxs.length} transactions failed:\n\t${errTxs
|
||||||
|
.map(
|
||||||
|
a =>
|
||||||
|
`tx: ${a.tx_id}\noperation: ${JSON.stringify(
|
||||||
|
nonceToOperation.get(a.nonce) ?? 'N/A',
|
||||||
|
jsonReplacer,
|
||||||
|
)}, result: ${JSON.stringify(a.tx_result)}`,
|
||||||
|
)
|
||||||
|
.join('\n\t')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Finished ${nonce - startingNonce} transactions in ${
|
||||||
|
Date.now() - start
|
||||||
|
}ms`,
|
||||||
|
);
|
||||||
|
return nonce;
|
||||||
|
};
|
||||||
|
|
||||||
|
const network = new StacksMocknet({ url: STACKS_API_URL() });
|
||||||
|
|
||||||
|
function hashCode(str: string) {
|
||||||
|
let hash = 0,
|
||||||
|
i = 0,
|
||||||
|
len = str.length;
|
||||||
|
while (i < len) {
|
||||||
|
hash = ((hash << 5) - hash + str.charCodeAt(i++)) << 0;
|
||||||
|
}
|
||||||
|
return hash + 2147483647 + 1;
|
||||||
|
// return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all ERR- for debug purposes
|
||||||
|
const codeMap: {
|
||||||
|
[code: string]: {
|
||||||
|
code: string;
|
||||||
|
comment: string;
|
||||||
|
};
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
function processError(name: string, input: string) {
|
||||||
|
const lines = input.split('\n');
|
||||||
|
const result = lines
|
||||||
|
.map((line, index) => {
|
||||||
|
if (line.includes('define-constant')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
if (!line.includes('ERR-')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
const location = `${name}.clar:${index + 1}`;
|
||||||
|
const code = hashCode(location).toString();
|
||||||
|
const searchValue = /ERR-[A-Z-]+/g;
|
||||||
|
codeMap[code] = {
|
||||||
|
code: line.match(searchValue)?.join(',') ?? 'UNKNOWN_CODE',
|
||||||
|
comment: location,
|
||||||
|
};
|
||||||
|
return line.replaceAll(searchValue, `(err u${code})`); //?
|
||||||
|
})
|
||||||
|
.filter(x => Boolean(x) && !x.startsWith(';;'))
|
||||||
|
.join('\n');
|
||||||
|
fs.writeFileSync('./codeMap.json', JSON.stringify(codeMap, null, 2) + '\n', {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deployContract(
|
||||||
|
operation: Operation.DeployContract,
|
||||||
|
options: OperationOptions,
|
||||||
|
) {
|
||||||
|
const txOptions = {
|
||||||
|
contractName: operation.name,
|
||||||
|
codeBody: processError(
|
||||||
|
operation.name,
|
||||||
|
fs.readFileSync(operation.path, 'utf8'),
|
||||||
|
),
|
||||||
|
nonce: options.nonce,
|
||||||
|
network,
|
||||||
|
anchorMode: AnchorMode.Any,
|
||||||
|
postConditionMode: PostConditionMode.Allow,
|
||||||
|
senderKey: options.senderKey,
|
||||||
|
fee: options.fee,
|
||||||
|
};
|
||||||
|
const fee = await estimateContractFunctionCall(
|
||||||
|
await makeContractDeploy(txOptions),
|
||||||
|
network,
|
||||||
|
).catch(() => options.fee);
|
||||||
|
const result = await broadcastTransaction(
|
||||||
|
await makeContractDeploy({
|
||||||
|
...txOptions,
|
||||||
|
fee,
|
||||||
|
}),
|
||||||
|
network,
|
||||||
|
);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(result.reason!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transferSTX(
|
||||||
|
operation: Operation.TransferSTX,
|
||||||
|
options: OperationOptions,
|
||||||
|
) {
|
||||||
|
const txOptions = {
|
||||||
|
network,
|
||||||
|
nonce: options.nonce,
|
||||||
|
fee: options.fee,
|
||||||
|
anchorMode: AnchorMode.Any,
|
||||||
|
postConditionMode: PostConditionMode.Allow,
|
||||||
|
senderKey: options.senderKey,
|
||||||
|
amount: operation.amount,
|
||||||
|
recipient: operation.address,
|
||||||
|
};
|
||||||
|
const fee = await estimateContractFunctionCall(
|
||||||
|
await makeSTXTokenTransfer(txOptions),
|
||||||
|
network,
|
||||||
|
).catch(() => options.fee);
|
||||||
|
const result = await broadcastTransaction(
|
||||||
|
await makeSTXTokenTransfer({
|
||||||
|
...txOptions,
|
||||||
|
fee,
|
||||||
|
}),
|
||||||
|
network,
|
||||||
|
);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(result.reason!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationOptions = {
|
||||||
|
senderKey: string;
|
||||||
|
nonce: number;
|
||||||
|
fee?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function publicCall(
|
||||||
|
operation: Operation.PublicCall,
|
||||||
|
options: OperationOptions,
|
||||||
|
) {
|
||||||
|
const txOptions = {
|
||||||
|
network,
|
||||||
|
contractAddress: DEPLOYER_ACCOUNT_ADDRESS(),
|
||||||
|
contractName: operation.contract,
|
||||||
|
functionName: operation.function,
|
||||||
|
functionArgs: operation.args,
|
||||||
|
nonce: options.nonce,
|
||||||
|
fee: options.fee,
|
||||||
|
anchorMode: AnchorMode.Any,
|
||||||
|
postConditionMode: PostConditionMode.Allow,
|
||||||
|
senderKey: options.senderKey,
|
||||||
|
};
|
||||||
|
const fee = await estimateContractFunctionCall(
|
||||||
|
await makeContractCall(txOptions),
|
||||||
|
network,
|
||||||
|
).catch(() => options.fee);
|
||||||
|
const result = await broadcastTransaction(
|
||||||
|
await makeContractCall({
|
||||||
|
...txOptions,
|
||||||
|
fee,
|
||||||
|
}),
|
||||||
|
network,
|
||||||
|
);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(result.reason!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccountInfo(
|
||||||
|
address: string,
|
||||||
|
): Promise<AccountDataResponse> {
|
||||||
|
const url = `${STACKS_API_URL()}/v2/accounts/${address}?proof=0`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
return await res.json().catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTransaction(address: string, untilNonce: number) {
|
||||||
|
let result: Transaction[] = [];
|
||||||
|
while (result.every(t => t.nonce > untilNonce)) {
|
||||||
|
const response: AddressTransactionsListResponse = await fetch(
|
||||||
|
`${STACKS_API_URL()}/extended/v1/address/${address}/transactions?limit=50&offset=${
|
||||||
|
result.length
|
||||||
|
}`,
|
||||||
|
).then(r => r.json());
|
||||||
|
const newResults = response.results as any[];
|
||||||
|
if (!newResults.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push(...newResults);
|
||||||
|
}
|
||||||
|
return result.filter(a => a.nonce >= untilNonce);
|
||||||
|
}
|
||||||
230
codeMap.json
Normal file
230
codeMap.json
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
{
|
||||||
|
"377555010": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:108"
|
||||||
|
},
|
||||||
|
"377555038": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:115"
|
||||||
|
},
|
||||||
|
"377555067": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:123"
|
||||||
|
},
|
||||||
|
"377555068": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:124"
|
||||||
|
},
|
||||||
|
"377555069": {
|
||||||
|
"code": "ERR-TOO-MANY-TXINS",
|
||||||
|
"comment": "clarity-bitcoin.clar:125"
|
||||||
|
},
|
||||||
|
"377555160": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:153"
|
||||||
|
},
|
||||||
|
"377555163": {
|
||||||
|
"code": "ERR-TOO-MANY-TXINS",
|
||||||
|
"comment": "clarity-bitcoin.clar:156"
|
||||||
|
},
|
||||||
|
"377555191": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:163"
|
||||||
|
},
|
||||||
|
"377555192": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:164"
|
||||||
|
},
|
||||||
|
"377555193": {
|
||||||
|
"code": "ERR-TOO-MANY-TXINS",
|
||||||
|
"comment": "clarity-bitcoin.clar:165"
|
||||||
|
},
|
||||||
|
"377555220": {
|
||||||
|
"code": "ERR-TOO-MANY-TXINS",
|
||||||
|
"comment": "clarity-bitcoin.clar:171"
|
||||||
|
},
|
||||||
|
"377555225": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:176"
|
||||||
|
},
|
||||||
|
"377555226": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:177"
|
||||||
|
},
|
||||||
|
"377555227": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:178"
|
||||||
|
},
|
||||||
|
"377555285": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:194"
|
||||||
|
},
|
||||||
|
"377555287": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:196"
|
||||||
|
},
|
||||||
|
"377555964": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:201"
|
||||||
|
},
|
||||||
|
"377555965": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:202"
|
||||||
|
},
|
||||||
|
"377555966": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:203"
|
||||||
|
},
|
||||||
|
"377555972": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:209"
|
||||||
|
},
|
||||||
|
"377556028": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:223"
|
||||||
|
},
|
||||||
|
"377556030": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:225"
|
||||||
|
},
|
||||||
|
"377556213": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:282"
|
||||||
|
},
|
||||||
|
"377556214": {
|
||||||
|
"code": "ERR-VARSLICE-TOO-LONG",
|
||||||
|
"comment": "clarity-bitcoin.clar:283"
|
||||||
|
},
|
||||||
|
"377556215": {
|
||||||
|
"code": "ERR-TOO-MANY-TXOUTS",
|
||||||
|
"comment": "clarity-bitcoin.clar:284"
|
||||||
|
},
|
||||||
|
"377556216": {
|
||||||
|
"code": "ERR-TOO-MANY-TXINS",
|
||||||
|
"comment": "clarity-bitcoin.clar:285"
|
||||||
|
},
|
||||||
|
"377558043": {
|
||||||
|
"code": "ERR-PROOF-TOO-SHORT",
|
||||||
|
"comment": "clarity-bitcoin.clar:453"
|
||||||
|
},
|
||||||
|
"377558046": {
|
||||||
|
"code": "ERR-PROOF-TOO-SHORT",
|
||||||
|
"comment": "clarity-bitcoin.clar:456"
|
||||||
|
},
|
||||||
|
"377558108": {
|
||||||
|
"code": "ERR-PROOF-TOO-SHORT",
|
||||||
|
"comment": "clarity-bitcoin.clar:476"
|
||||||
|
},
|
||||||
|
"2090389179": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:15"
|
||||||
|
},
|
||||||
|
"2090389183": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:19"
|
||||||
|
},
|
||||||
|
"2090389209": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:24"
|
||||||
|
},
|
||||||
|
"2090389213": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:28"
|
||||||
|
},
|
||||||
|
"2090389239": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:33"
|
||||||
|
},
|
||||||
|
"2090389243": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:37"
|
||||||
|
},
|
||||||
|
"2090389269": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:42"
|
||||||
|
},
|
||||||
|
"2090389273": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:46"
|
||||||
|
},
|
||||||
|
"2090389299": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:51"
|
||||||
|
},
|
||||||
|
"2090389304": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:56"
|
||||||
|
},
|
||||||
|
"2090389367": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:77"
|
||||||
|
},
|
||||||
|
"2090389395": {
|
||||||
|
"code": "ERR-OUT-OF-BOUNDS",
|
||||||
|
"comment": "clarity-bitcoin.clar:84"
|
||||||
|
},
|
||||||
|
"2894909962": {
|
||||||
|
"code": "ERR-VALIDATOR-ALREADY-REGISTERED",
|
||||||
|
"comment": "indexer.clar:70"
|
||||||
|
},
|
||||||
|
"2894909993": {
|
||||||
|
"code": "ERR-UNKNOWN-VALIDATOR",
|
||||||
|
"comment": "indexer.clar:80"
|
||||||
|
},
|
||||||
|
"2894910031": {
|
||||||
|
"code": "ERR-REQUIRED-VALIDATORS",
|
||||||
|
"comment": "indexer.clar:97"
|
||||||
|
},
|
||||||
|
"3842857249": {
|
||||||
|
"code": "ERR-UNKNOWN-VALIDATOR",
|
||||||
|
"comment": "indexer.clar:123"
|
||||||
|
},
|
||||||
|
"3842857315": {
|
||||||
|
"code": "ERR-DUPLICATE-SIGNATURE",
|
||||||
|
"comment": "indexer.clar:147"
|
||||||
|
},
|
||||||
|
"3842857316": {
|
||||||
|
"code": "ERR-ORDER-HASH-MISMATCH",
|
||||||
|
"comment": "indexer.clar:148"
|
||||||
|
},
|
||||||
|
"3842857317": {
|
||||||
|
"code": "ERR-INVALID-SIGNATURE",
|
||||||
|
"comment": "indexer.clar:149"
|
||||||
|
},
|
||||||
|
"3842857347": {
|
||||||
|
"code": "ERR-TX-NOT-INDEXED",
|
||||||
|
"comment": "indexer.clar:158"
|
||||||
|
},
|
||||||
|
"3842857404": {
|
||||||
|
"code": "ERR-PAUSED",
|
||||||
|
"comment": "indexer.clar:173"
|
||||||
|
},
|
||||||
|
"3842857405": {
|
||||||
|
"code": "ERR-UKNOWN-RELAYER",
|
||||||
|
"comment": "indexer.clar:174"
|
||||||
|
},
|
||||||
|
"3842858185": {
|
||||||
|
"code": "ERR-TX-ALREADY-INDEXED",
|
||||||
|
"comment": "indexer.clar:219"
|
||||||
|
},
|
||||||
|
"3842858207": {
|
||||||
|
"code": "ERR-REQUIRED-VALIDATORS",
|
||||||
|
"comment": "indexer.clar:220"
|
||||||
|
},
|
||||||
|
"3842858208": {
|
||||||
|
"code": "ERR-DOUBLE-SPEND",
|
||||||
|
"comment": "indexer.clar:221"
|
||||||
|
},
|
||||||
|
"3842858209": {
|
||||||
|
"code": "ERR-FROM-BAL-MISMATCH",
|
||||||
|
"comment": "indexer.clar:222"
|
||||||
|
},
|
||||||
|
"3842858210": {
|
||||||
|
"code": "ERR-TO-BAL-MISMATCH",
|
||||||
|
"comment": "indexer.clar:223"
|
||||||
|
},
|
||||||
|
"3842858269": {
|
||||||
|
"code": "ERR-NOT-AUTHORIZED",
|
||||||
|
"comment": "indexer.clar:240"
|
||||||
|
}
|
||||||
|
}
|
||||||
78
dev-stack/devenv
Executable file
78
dev-stack/devenv
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
PROFILE=${DOCKER_COMPOSE_PROFILE:-'dev'}
|
||||||
|
export NODE_NO_WARNINGS=1
|
||||||
|
|
||||||
|
pushd "$DIR" > /dev/null 2>&1 || exit
|
||||||
|
if [ ! -e "./wait" ]; then
|
||||||
|
wget -qO ./wait https://github.com/ufoscout/docker-compose-wait/releases/download/2.12.0/wait
|
||||||
|
chmod +x ./wait
|
||||||
|
fi
|
||||||
|
popd > /dev/null 2>&1 || exit
|
||||||
|
|
||||||
|
_docker_compose() {
|
||||||
|
pushd "$DIR" >/dev/null 2>&1 || exit
|
||||||
|
docker-compose --profile $PROFILE --project-name brc20-indexer -f stacks-blockchain/docker-compose.yml $@
|
||||||
|
popd >/dev/null 2>&1 || exit
|
||||||
|
}
|
||||||
|
|
||||||
|
read_docker_compose() {
|
||||||
|
yq e "$@" dev-stack/stacks-blockchain/docker-compose.yml
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_ports() {
|
||||||
|
echo postgres: "$(read_docker_compose .services.postgres.ports[0] | cut -d: -f1)"
|
||||||
|
echo stacks-blockchain-api: "$(read_docker_compose .services.stacks-blockchain-api.ports[0] | cut -d: -f1)"
|
||||||
|
echo stacks-blockchain: "$(read_docker_compose .services.stacks-blockchain.ports[0] | cut -d: -f1)"
|
||||||
|
echo stacks-blockchain-explorer: "$(read_docker_compose .services.stacks-blockchain-explorer.ports[0] | cut -d: -f1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd=$1
|
||||||
|
case $cmd in
|
||||||
|
up)
|
||||||
|
shift
|
||||||
|
_docker_compose up $@
|
||||||
|
echo_ports
|
||||||
|
;;
|
||||||
|
upd)
|
||||||
|
_docker_compose up -d
|
||||||
|
echo_ports
|
||||||
|
;;
|
||||||
|
down)
|
||||||
|
_docker_compose down $@
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
_docker_compose logs -f --tail=100
|
||||||
|
;;
|
||||||
|
clean)
|
||||||
|
_docker_compose down -v -t 0
|
||||||
|
;;
|
||||||
|
reset)
|
||||||
|
_docker_compose down -v -t 0
|
||||||
|
_docker_compose up -d
|
||||||
|
echo_ports
|
||||||
|
echo -n "Waiting for stacks blockchain node"
|
||||||
|
for _ in $(seq 1 999); do
|
||||||
|
echo -n .
|
||||||
|
if curl -so /dev/null http://localhost:$(read_docker_compose .services.stacks-blockchain.ports[0] | cut -d: -f1); then
|
||||||
|
echo
|
||||||
|
echo 'stacks blockchain node started'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
echo)
|
||||||
|
echo_ports
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {up|down|logs|clean|reset}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main $@
|
||||||
33
dev-stack/stacks-blockchain/config/Stacks.toml
Normal file
33
dev-stack/stacks-blockchain/config/Stacks.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[node]
|
||||||
|
working_dir = "/root/stacks-node/data"
|
||||||
|
rpc_bind = "0.0.0.0:20443"
|
||||||
|
p2p_bind = "0.0.0.0:20444"
|
||||||
|
wait_time_for_microblocks = 1000
|
||||||
|
use_test_genesis_chainstate = true
|
||||||
|
enable_puppet_mode = true
|
||||||
|
|
||||||
|
[[events_observer]]
|
||||||
|
endpoint = "stacks-blockchain-api:3700"
|
||||||
|
retry_count = 255
|
||||||
|
events_keys = ["*"]
|
||||||
|
|
||||||
|
[burnchain]
|
||||||
|
chain = "bitcoin"
|
||||||
|
mode = "mocknet"
|
||||||
|
commit_anchor_block_within = 0
|
||||||
|
|
||||||
|
[[mstx_balance]]
|
||||||
|
address = "ST19BH99Z7P8FSJ58EYPZ13CJJNYHC6GVMMM2T1B3"
|
||||||
|
amount = 100_000_000_000_000
|
||||||
|
|
||||||
|
[[mstx_balance]]
|
||||||
|
address = "STP7HH9H64RQH870ZB9JJWE212QB0HJ1FN5GSGTQ"
|
||||||
|
amount = 100_000_000_000_000
|
||||||
|
|
||||||
|
[connection_options]
|
||||||
|
public_ip_address = "127.0.0.1:20444"
|
||||||
|
read_only_call_limit_write_length = 15000000
|
||||||
|
read_only_call_limit_read_length = 100000000
|
||||||
|
read_only_call_limit_write_count = 15500
|
||||||
|
read_only_call_limit_read_count = 15500
|
||||||
|
read_only_call_limit_runtime = 5000000000
|
||||||
86
dev-stack/stacks-blockchain/docker-compose.yml
Normal file
86
dev-stack/stacks-blockchain/docker-compose.yml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15.4-alpine
|
||||||
|
container_name: brc20_indexer_stacks_pg
|
||||||
|
command: postgres -c 'max_connections=1000'
|
||||||
|
profiles: ['dev', 'ci']
|
||||||
|
shm_size: 1gb
|
||||||
|
ports:
|
||||||
|
- '19432:5432'
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: 4xQUdohS4oIn8pKW
|
||||||
|
POSTGRES_DB: stacks_blockchain_api
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||||
|
stacks-blockchain-api:
|
||||||
|
image: bestmike007/stacks-blockchain-api:7.3.0
|
||||||
|
container_name: brc20_indexer_stacks_api
|
||||||
|
profiles: ['dev', 'ci']
|
||||||
|
command: sh -c "/wait && node ./lib/index.js"
|
||||||
|
ports:
|
||||||
|
- '19999:3999'
|
||||||
|
environment:
|
||||||
|
WAIT_HOSTS: 'postgres:5432'
|
||||||
|
PG_HOST: postgres
|
||||||
|
PG_PORT: 5432
|
||||||
|
PG_USER: postgres
|
||||||
|
PG_PASSWORD: 4xQUdohS4oIn8pKW
|
||||||
|
PG_DATABASE: stacks_blockchain_api
|
||||||
|
PG_SCHEMA: stacks_blockchain_api
|
||||||
|
STACKS_CORE_EVENT_PORT: '3700'
|
||||||
|
STACKS_CORE_EVENT_HOST: http://0.0.0.0
|
||||||
|
STACKS_BLOCKCHAIN_API_PORT: '3999'
|
||||||
|
STACKS_BLOCKCHAIN_API_HOST: 0.0.0.0
|
||||||
|
STACKS_CORE_RPC_HOST: stacks-blockchain
|
||||||
|
STACKS_CORE_RPC_PORT: '20443'
|
||||||
|
STACKS_CHAIN_ID: '0x80000000'
|
||||||
|
NODE_ENV: development
|
||||||
|
STACKS_API_ENABLE_FT_METADATA: 1
|
||||||
|
STACKS_API_ENABLE_NFT_METADATA: 1
|
||||||
|
volumes:
|
||||||
|
- $PWD/wait:/wait
|
||||||
|
stacks-blockchain:
|
||||||
|
image: bestmike007/stacks-blockchain:alex-v20230808
|
||||||
|
container_name: brc20_indexer_stacks_node
|
||||||
|
profiles: ['dev', 'ci']
|
||||||
|
command: sh -c "/wait && stacks-node start --config=/app/config/Stacks.toml"
|
||||||
|
ports:
|
||||||
|
- '19443:20443'
|
||||||
|
- '19445:20445'
|
||||||
|
environment:
|
||||||
|
WAIT_BEFORE: 3
|
||||||
|
WAIT_AFTER: 3
|
||||||
|
WAIT_HOSTS: 'stacks-blockchain-api:3700'
|
||||||
|
NOP_BLOCKSTACK_DEBUG: 1
|
||||||
|
XBLOCKSTACK_DEBUG: 1
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
STACKS_CHAIN_ID: '0x80000000'
|
||||||
|
V2_POX_MIN_AMOUNT_USTX: 90000000260
|
||||||
|
STACKS_CORE_RPC_HOST: stacks-blockchain
|
||||||
|
STACKS_CORE_RPC_PORT: 20443
|
||||||
|
STACKS_API_ENABLE_FT_METADATA: 1
|
||||||
|
STACKS_API_ENABLE_NFT_METADATA: 1
|
||||||
|
STACKS_NODE_PUPPET_MODE: 'true'
|
||||||
|
volumes:
|
||||||
|
- stacks_blockchain_chaindata:/root/stacks-node/data
|
||||||
|
- $PWD/stacks-blockchain/config:/app/config
|
||||||
|
- $PWD/wait:/wait
|
||||||
|
stacks-blockchain-explorer:
|
||||||
|
image: hirosystems/explorer:1.39.0
|
||||||
|
container_name: brc20_indexer_stacks_explorer
|
||||||
|
profiles: ['dev']
|
||||||
|
ports:
|
||||||
|
- '19000:3000'
|
||||||
|
extra_hosts:
|
||||||
|
- 'gateway.docker.internal:host-gateway'
|
||||||
|
environment:
|
||||||
|
MAINNET_API_SERVER: http://gateway.docker.internal:18999
|
||||||
|
NEXT_PUBLIC_MAINNET_API_SERVER: ${PUBLIC_MAINNET_API_SERVER:-http://gateway.docker.internal:18999}
|
||||||
|
NEXT_PUBLIC_MAINNET_ENABLED: 'true'
|
||||||
|
NODE_ENV: development
|
||||||
|
volumes:
|
||||||
|
pgdata: {}
|
||||||
|
stacks_blockchain_chaindata: {}
|
||||||
1
dev-stack/stacks-blockchain/init.sql
Normal file
1
dev-stack/stacks-blockchain/init.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CREATE SCHEMA stacks_blockchain_api;
|
||||||
BIN
dev-stack/wait
Executable file
BIN
dev-stack/wait
Executable file
Binary file not shown.
1
env/.gitignore
vendored
Normal file
1
env/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.current_project
|
||||||
12
env/local/bootstrap/config.json
vendored
Normal file
12
env/local/bootstrap/config.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"DEPLOYER_ACCOUNT_ADDRESS": "ST19BH99Z7P8FSJ58EYPZ13CJJNYHC6GVMMM2T1B3",
|
||||||
|
"DEPLOYER_ACCOUNT_SECRETKEY": "c186582e7ec5c5febc121f73db95cfd90a16a80f53869a894cf934c828f1da5601",
|
||||||
|
"STACKS_API_URL": "http://localhost:19999",
|
||||||
|
"STACKS_PUPPET_URL": "http://localhost:19445/puppet/v1",
|
||||||
|
"USER_ACCOUNTS": [
|
||||||
|
{
|
||||||
|
"address": "STP7HH9H64RQH870ZB9JJWE212QB0HJ1FN5GSGTQ",
|
||||||
|
"senderKey": "de9e95d04469551c4eb7a0127155e51024f0e4ea8b1ae4649de227180e85d1c801"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
53
env/use.sh
vendored
Executable file
53
env/use.sh
vendored
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
BASE="$( cd "$( dirname "$0" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
|
||||||
|
|
||||||
|
pushd "$BASE"/../
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local ENV="$1"
|
||||||
|
|
||||||
|
if [ "$ENV" = "base" -o ! -d "$ROOT/env/$ENV" ]; then
|
||||||
|
echo unkown env "$ENV"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo preparing env $ENV
|
||||||
|
export ENV
|
||||||
|
processEnvFolder "$ROOT/env/base"
|
||||||
|
processEnvFolder "$ROOT/env/$ENV"
|
||||||
|
echo "$ENV" > "$BASE"/.current_project
|
||||||
|
echo switched to env $ENV
|
||||||
|
}
|
||||||
|
|
||||||
|
processEnvFolder() {
|
||||||
|
if [ -d "$1" ]
|
||||||
|
then
|
||||||
|
pushd "$1"
|
||||||
|
FILES=$(find . -type f)
|
||||||
|
popd
|
||||||
|
for f in $FILES
|
||||||
|
do
|
||||||
|
writeTemplateFile "$1" "$f"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTemplateFile() {
|
||||||
|
SOURCE_PATH=$1
|
||||||
|
FILE_PATH=$2
|
||||||
|
FILE_DIR=$(dirname "${FILE_PATH}")
|
||||||
|
mkdir -p "$FILE_DIR"
|
||||||
|
rm -f "$ROOT"/"$FILE_PATH"
|
||||||
|
"$BASE"/mo "$SOURCE_PATH"/"$FILE_PATH" > "$ROOT"/"$FILE_PATH"
|
||||||
|
chmod 400 "$ROOT"/"$FILE_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "${1-}" ]
|
||||||
|
then
|
||||||
|
main "$1"
|
||||||
|
else
|
||||||
|
printf './use.sh ENV \nexample: ./use.sh dev'
|
||||||
|
fi
|
||||||
18
package.json
18
package.json
@@ -1,16 +1,34 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"use": "./env/use.sh",
|
||||||
|
"devenv": "./dev-stack/devenv",
|
||||||
|
"gen:list": "yarn ts-node --swc bootstrap/contracts/generateContractList.ts",
|
||||||
|
"deploy": "yarn ts-node --swc bootstrap/deploy.ts",
|
||||||
|
"gen": "yarn ts-node --swc bootstrap/contracts/generateContracts.ts",
|
||||||
|
"setup": "yarn ts-node --swc bootstrap/setup.ts",
|
||||||
|
"faucet": "yarn ts-node --swc bootstrap/faucet.ts",
|
||||||
|
"reset-dev": "yarn devenv reset && yarn gen:list && yarn deploy && yarn gen && yarn setup"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
"@noble/hashes": "^1.3.1",
|
"@noble/hashes": "^1.3.1",
|
||||||
"@stacks/keychain": "^4.3.8",
|
"@stacks/keychain": "^4.3.8",
|
||||||
|
"@stacks/network": "^6.5.5",
|
||||||
|
"@stacks/stacks-blockchain-api-types": "^7.3.0",
|
||||||
|
"@stacks/transactions": "^6.7.0",
|
||||||
"bignumber.js": "^9.1.1",
|
"bignumber.js": "^9.1.1",
|
||||||
"bitcoinjs-lib": "^6.1.3",
|
"bitcoinjs-lib": "^6.1.3",
|
||||||
|
"clarity-codegen": "^0.2.2",
|
||||||
|
"got-cjs": "^12.5.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"micro-btc-signer": "^0.4.2",
|
"micro-btc-signer": "^0.4.2",
|
||||||
"micro-stacks": "^1.2.1",
|
"micro-stacks": "^1.2.1",
|
||||||
"tsx": "^3.12.7"
|
"tsx": "^3.12.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.3.73",
|
"@swc/core": "^1.3.73",
|
||||||
|
"@types/lodash": "^4.14.197",
|
||||||
"@types/node": "^20.4.5",
|
"@types/node": "^20.4.5",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"prettier-plugin-organize-imports": "^3.2.3",
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
|
|||||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"strict": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"noEmit": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user