mirror of
https://github.com/Permissionless-Software-Foundation/psffpp-payments.git
synced 2026-09-21 16:52:04 -07:00
first commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Print deterministic P2SH32 addresses from a deploy config (read-only).
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/addresses.mjs [config/deploy.mainnet.json]
|
||||
*/
|
||||
import {
|
||||
loadDeployConfig,
|
||||
createProvider,
|
||||
instantiateContracts,
|
||||
buildDeploymentRecord,
|
||||
root
|
||||
} from './lib.mjs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const configPath = process.argv[2] || join(root, 'config', 'deploy.mainnet.json')
|
||||
const cfg = loadDeployConfig(configPath)
|
||||
const provider = createProvider(cfg.network || 'mainnet')
|
||||
const inst = instantiateContracts(cfg, provider)
|
||||
const record = buildDeploymentRecord(cfg, inst)
|
||||
|
||||
console.log(JSON.stringify({
|
||||
network: record.network,
|
||||
poolAddress: record.poolAddress,
|
||||
shares: record.nodes.map((n) => ({
|
||||
name: n.name,
|
||||
shareAddress: n.shareAddress,
|
||||
pkh: n.pkh
|
||||
})),
|
||||
splitBlockheight: record.splitBlockheight,
|
||||
minConsolidation: record.minConsolidation,
|
||||
artifacts: record.artifacts
|
||||
}, null, 2))
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { compileFile } from 'cashc'
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const contractsDir = join(root, 'contracts')
|
||||
const artifactsDir = join(root, 'artifacts')
|
||||
|
||||
mkdirSync(artifactsDir, { recursive: true })
|
||||
|
||||
const files = ['ShareContract.cash', 'PoolContract.cash']
|
||||
|
||||
for (const file of files) {
|
||||
const src = join(contractsDir, file)
|
||||
const artifact = compileFile(src)
|
||||
const outName = file.replace(/\.cash$/, '.json')
|
||||
const outPath = join(artifactsDir, outName)
|
||||
writeFileSync(outPath, JSON.stringify(artifact, null, 2) + '\n')
|
||||
console.log(`compiled ${file} -> artifacts/${outName}`)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Instantiate contracts against the configured network and write a deployment record.
|
||||
* Does not broadcast or move funds — funding is a normal send to poolAddress.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/deploy.mjs [config/deploy.mainnet.json]
|
||||
*/
|
||||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
loadDeployConfig,
|
||||
createProvider,
|
||||
instantiateContracts,
|
||||
buildDeploymentRecord,
|
||||
root
|
||||
} from './lib.mjs'
|
||||
|
||||
const configPath = process.argv[2] || join(root, 'config', 'deploy.mainnet.json')
|
||||
const cfg = loadDeployConfig(configPath)
|
||||
|
||||
if ((cfg.network || 'mainnet') === 'mainnet') {
|
||||
const placeholder = /^0{40}$|^1{40}$|^2{40}$|^3{40}$/i
|
||||
if (placeholder.test(cfg.treasuryPkh) || cfg.nodes.some((n) => placeholder.test(n.pkh) || /^(02a+|02b+|02c+)/i.test(n.pubkey))) {
|
||||
console.warn('WARNING: config still looks like example placeholders. Replace with real keys before funding.')
|
||||
}
|
||||
}
|
||||
|
||||
const provider = createProvider(cfg.network || 'mainnet')
|
||||
const inst = instantiateContracts(cfg, provider)
|
||||
const record = buildDeploymentRecord(cfg, inst)
|
||||
|
||||
const outDir = join(root, 'deployments')
|
||||
mkdirSync(outDir, { recursive: true })
|
||||
const stamp = record.createdAt.replace(/[:.]/g, '-')
|
||||
const outPath = join(outDir, `${stamp}.json`)
|
||||
writeFileSync(outPath, JSON.stringify(record, null, 2) + '\n')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
wrote: outPath,
|
||||
poolAddress: record.poolAddress,
|
||||
shareAddresses: record.nodes.map((n) => n.shareAddress),
|
||||
splitBlockheight: record.splitBlockheight,
|
||||
artifacts: record.artifacts
|
||||
}, null, 2))
|
||||
|
||||
console.log(`
|
||||
Next steps:
|
||||
1. Verify addresses match: npm run addresses -- ${configPath}
|
||||
2. Smoke-fund poolAddress with a small intentional amount
|
||||
3. Exercise consolidate / split / claim before accepting production deposits
|
||||
`)
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hexToBin, binToHex } from '@bitauth/libauth'
|
||||
import { Contract, ElectrumNetworkProvider, MockNetworkProvider } from 'cashscript'
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
export function loadArtifact (name) {
|
||||
return JSON.parse(readFileSync(join(root, 'artifacts', `${name}.json`), 'utf8'))
|
||||
}
|
||||
|
||||
export function artifactFingerprint (artifact) {
|
||||
return createHash('sha256')
|
||||
.update(JSON.stringify({ bytecode: artifact.bytecode, abi: artifact.abi, constructorInputs: artifact.constructorInputs }))
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
export function loadDeployConfig (configPath) {
|
||||
const path = resolve(configPath)
|
||||
if (!existsSync(path)) {
|
||||
throw new Error(`Config not found: ${path}. Copy config/deploy.mainnet.example.json to config/deploy.mainnet.json and fill real values.`)
|
||||
}
|
||||
const cfg = JSON.parse(readFileSync(path, 'utf8'))
|
||||
validateConfig(cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
function validateConfig (cfg) {
|
||||
if (!cfg.nodes || cfg.nodes.length !== 3) {
|
||||
throw new Error('config.nodes must contain exactly 3 entries (M=3)')
|
||||
}
|
||||
for (const [i, node] of cfg.nodes.entries()) {
|
||||
if (!/^[0-9a-fA-F]{66}$/.test(node.pubkey)) {
|
||||
throw new Error(`nodes[${i}].pubkey must be 33-byte compressed hex (66 chars)`)
|
||||
}
|
||||
if (!/^[0-9a-fA-F]{40}$/.test(node.pkh)) {
|
||||
throw new Error(`nodes[${i}].pkh must be 20-byte hex (40 chars)`)
|
||||
}
|
||||
}
|
||||
if (!/^[0-9a-fA-F]{40}$/.test(cfg.treasuryPkh)) {
|
||||
throw new Error('treasuryPkh must be 20-byte hex (40 chars)')
|
||||
}
|
||||
if (!Number.isInteger(cfg.minConsolidation) || cfg.minConsolidation < 1) {
|
||||
throw new Error('minConsolidation must be a positive integer')
|
||||
}
|
||||
if (!Number.isInteger(cfg.splitBlockheight) || cfg.splitBlockheight < 1) {
|
||||
throw new Error('splitBlockheight must be a positive integer (block height)')
|
||||
}
|
||||
}
|
||||
|
||||
export function createProvider (network) {
|
||||
if (network === 'mocknet') return new MockNetworkProvider()
|
||||
return new ElectrumNetworkProvider(network || 'mainnet')
|
||||
}
|
||||
|
||||
export function instantiateContracts (cfg, provider) {
|
||||
const shareArtifact = loadArtifact('ShareContract')
|
||||
const poolArtifact = loadArtifact('PoolContract')
|
||||
|
||||
const pubs = cfg.nodes.map((n) => hexToBin(n.pubkey))
|
||||
const shares = cfg.nodes.map((node) =>
|
||||
new Contract(
|
||||
shareArtifact,
|
||||
[...pubs, hexToBin(node.pkh)],
|
||||
{ provider }
|
||||
)
|
||||
)
|
||||
|
||||
const pool = new Contract(
|
||||
poolArtifact,
|
||||
[
|
||||
...pubs,
|
||||
hexToBin(cfg.treasuryPkh),
|
||||
hexToBin(shares[0].lockingBytecode),
|
||||
hexToBin(shares[1].lockingBytecode),
|
||||
hexToBin(shares[2].lockingBytecode),
|
||||
BigInt(cfg.minConsolidation),
|
||||
BigInt(cfg.splitBlockheight)
|
||||
],
|
||||
{ provider }
|
||||
)
|
||||
|
||||
return {
|
||||
shares,
|
||||
pool,
|
||||
fingerprints: {
|
||||
ShareContract: artifactFingerprint(shareArtifact),
|
||||
PoolContract: artifactFingerprint(poolArtifact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildDeploymentRecord (cfg, { shares, pool, fingerprints }) {
|
||||
return {
|
||||
network: cfg.network || 'mainnet',
|
||||
createdAt: new Date().toISOString(),
|
||||
minConsolidation: cfg.minConsolidation,
|
||||
splitBlockheight: cfg.splitBlockheight,
|
||||
treasuryPkh: cfg.treasuryPkh.toLowerCase(),
|
||||
nodes: cfg.nodes.map((n, i) => ({
|
||||
name: n.name || `node${i}`,
|
||||
pubkey: n.pubkey.toLowerCase(),
|
||||
pkh: n.pkh.toLowerCase(),
|
||||
shareAddress: shares[i].address,
|
||||
shareLockingBytecode: shares[i].lockingBytecode
|
||||
})),
|
||||
poolAddress: pool.address,
|
||||
poolLockingBytecode: pool.lockingBytecode,
|
||||
artifacts: fingerprints,
|
||||
constructorNotes: {
|
||||
M: 3,
|
||||
K: 2,
|
||||
deployOrder: 'ShareContracts first, then PoolContract baking share locking bytecodes',
|
||||
epochModel: 'One splitBlockheight per deploy; next month redeploy + K-of-M migrate'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { root, binToHex, hexToBin }
|
||||
Reference in New Issue
Block a user