support git3 create && bug fix

master
cyhhao 2 years ago
parent b6e6286b23
commit c2956f0c41

@ -0,0 +1,4 @@
{
"prettier.tabWidth": 4,
"vetur.format.options.tabSize": 4
}

@ -28,9 +28,8 @@ git3://[sender_wallet]@[hub_contract_address or NS]:[chain_id]/<repo_name>
- <repo_name> Required, your repo name
## Example:
- `git3://helloworld`
select `default` wallet, `git3 official hub contract` address, on ETHStorage chainId: 3334, repo name is `helloworld`
It's equl to `git3://default@git3.w3q:3334/helloworld`
- `git3://helloworld@git3.fvm/helloworld`
select `default` wallet, `git3 official hub contract` address, on FVM chainId: 3141, repo name is `helloworld`
- `git3://myname.eth@git3hub.eth/helloworld`
select `myname.eth` wallet, `git3hub.eth` hub contract address, on ETH Mainnet chainId: 1, repo name is `helloworld`

@ -0,0 +1,98 @@
import { ethers } from "ethers"
import nameServices from "../config/name-services.js"
import { ETHStorage } from "../storage/ETHStorage.js"
import { SLIStorage } from "../storage/SLIStorage.js"
import { getWallet, randomRPC, setupContract } from "./wallet.js"
import network from "../config/evm-network.js"
import abis from "../config/abis.js"
export type Git3Protocol = {
sender: string
senderAddress: string
hubAddress: string
repoName: string
chainId: number
netConfig: Record<string, any>
wallet: ethers.Wallet
contract: ethers.Contract
storageClass: any
ns?: Record<string, any>
nsName?: string
nsDomain?: string
}
type Option = {
skipRepoName: boolean
}
export function parseGit3URI(
uri: string,
option: Option = { skipRepoName: false }
): Git3Protocol {
const url = new URL(uri)
let sender = url.username || "default"
let chainId = url.port ? parseInt(url.port) : null
let hub = url.hostname
let hubAddress
let nsName, nsDomain, ns
if (!hub) throw new Error("invalid git3 uri, no hub address")
let repoName = url.pathname.slice(1)
if (!option.skipRepoName && !repoName)
throw new Error("invalid git3 uri, no repo name")
if (hub.indexOf(".") < 0) {
if (url.hostname.startsWith("0x")) {
hubAddress = url.hostname
} else {
throw new Error("invalid git3 uri, hub must be NS or address")
}
} else {
;[nsName, nsDomain] = url.hostname.split(".")
ns = nameServices[nsDomain]
if (!ns) throw new Error("invalid name service")
chainId = chainId || ns.chainId
// Todo: resolve name service
// hubAddress = ns.resolver()
}
if (!chainId) throw new Error("invalid git3 uri, no chainId")
let netConfig = network[chainId]
if (!netConfig) throw new Error("invalid chainId")
if (!hubAddress) hubAddress = netConfig.contracts.git3
let wallet = getWallet(sender)
let senderAddress = wallet.address
// route to different storage
let storageClass, abi
if (chainId == 3334) {
storageClass = ETHStorage
abi = abis.ETHStorage
} else {
storageClass = SLIStorage
abi = abis.SLIStorage
}
let rpc = randomRPC(netConfig.rpc)
const provider = new ethers.providers.JsonRpcProvider(rpc)
let contract = setupContract(provider, hubAddress, abi, wallet)
wallet = wallet.connect(contract.provider)
return {
sender,
senderAddress,
hubAddress,
repoName,
chainId,
netConfig,
wallet,
contract,
storageClass,
ns,
nsName,
nsDomain,
}
}

@ -11,16 +11,18 @@ export class TxManager {
gasLimitRatio: number
minNonce: number = -1
queueCurrNonce: number = -1
highestNonce: number = -1
rbfTimes: number
boardcastTimes: number
waitDistance: number
minRBFRatio: number
_initialPromise: Promise<number> | null = null
_deltaCount: number = 0
constructor(
contract: ethers.Contract,
chainId: number,
constOptions: {
constOptions?: {
blockTimeSec?: number
gasLimitRatio?: number
rbfTimes?: number
@ -33,12 +35,12 @@ export class TxManager {
this.contract = contract
this.price = null
this.cancel = false
this.blockTimeSec = constOptions.blockTimeSec || 3
this.gasLimitRatio = constOptions.gasLimitRatio || 1.2
this.rbfTimes = constOptions.rbfTimes || 3
this.boardcastTimes = constOptions.boardcastTimes || 3
this.waitDistance = constOptions.waitDistance || 10
this.minRBFRatio = constOptions.minRBFRatio || 1.3
this.blockTimeSec = constOptions?.blockTimeSec || 3
this.gasLimitRatio = constOptions?.gasLimitRatio || 1.2
this.rbfTimes = constOptions?.rbfTimes || 3
this.boardcastTimes = constOptions?.boardcastTimes || 3
this.waitDistance = constOptions?.waitDistance || 10
this.minRBFRatio = constOptions?.minRBFRatio || 1.3
}
async FreshBaseGas(): Promise<ethers.providers.FeeData | null> {
@ -51,18 +53,50 @@ export class TxManager {
// TODO: cancel all tx sended
}
async clearPendingNonce(num: number = 1, rbfRatio: number = 1.5) {
const signer = this.contract.signer
let nonce = await this.getNonce()
this._deltaCount++
console.log("clearPendingNonce", nonce, num)
let price = await this.FreshBaseGas()
let txs = []
for (let i = 0; i < num; i++) {
let res = signer.sendTransaction({
to: await signer.getAddress(),
nonce: nonce + i,
gasLimit: 21000,
type: 2,
chainId: this.chainId,
maxFeePerGas: price!
.maxFeePerGas!.mul((rbfRatio * 100) | 0)
.div(100),
maxPriorityFeePerGas: price!
.maxPriorityFeePerGas!.mul((rbfRatio * 100) | 0)
.div(100),
})
txs.push(res)
}
await Promise.all(txs)
}
async getNonce(): Promise<number> {
if (!this._initialPromise) {
this._initialPromise =
this.contract.signer.getTransactionCount("pending")
}
const deltaCount = this._deltaCount
this._deltaCount++
return this._initialPromise.then((initial) => initial + deltaCount)
}
async SendCall(_method: string, _args: any[]): Promise<any> {
const nonce = await this.getNonce()
let unsignedTx = await this.contract.populateTransaction[_method](
..._args
)
unsignedTx.chainId = this.chainId
if (this.highestNonce < 0) {
this.highestNonce = await this.contract.signer.getTransactionCount()
}
const nonce = this.highestNonce
unsignedTx.nonce = nonce
this.highestNonce += 1
unsignedTx.chainId = this.chainId
// estimateGas check
let gasLimit = await this.contract.provider.estimateGas(unsignedTx)
unsignedTx.gasLimit = gasLimit
@ -149,6 +183,8 @@ export class TxManager {
} catch (e: Error | any) {
if (e.code == ethers.errors.NONCE_EXPIRED) {
// ignore if tx already in mempool
} else if (e.code == ethers.errors.SERVER_ERROR) {
// ignore if tx already in mempool
} else {
console.error(
"[tx-manager] sendTransaction",

@ -0,0 +1,38 @@
import { mkdirSync, readFileSync } from "fs"
import { ethers } from "ethers"
export function getWallet(wallet: string | null = "default"): ethers.Wallet {
if (!wallet) wallet = "default"
// Todo: 0xaddress find wallet
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const content = readFileSync(`${keyPath}/${wallet}`).toString()
const [walletType, key] = content.split("\n")
let etherWallet =
walletType === "privateKey"
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
return etherWallet
}
export function setupContract(
provider: ethers.providers.JsonRpcProvider,
hubAddress: string,
abi: string,
wallet: ethers.Wallet
): ethers.Contract {
let contract = new ethers.Contract(hubAddress, abi, provider)
wallet = wallet.connect(provider)
contract = contract.connect(wallet)
return contract
}
export function randomRPC(rpcs: string[]): string {
return rpcs[Math.floor(Math.random() * rpcs.length)]
}

@ -2,7 +2,7 @@
const evmNetworks: Record<number, any> = {
1: {
name: "ethereum",
name: "Ethereum",
nativeCurrency: {
name: "Ether",
symbol: "ETH",

@ -1,10 +1,7 @@
import GitRemoteHelper from "./git-remote-helper.js"
import { ApiBaseParams } from "./git-remote-helper.js"
import Git from "./git.js"
import { ETHStorage } from "../storage/ETHStorage.js"
import nameServices from "../config/name-services.js"
import { SLIStorage } from "../storage/SLIStorage.js"
import { parseGit3URI } from "../common/git3-protocol.js"
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
let git: Git
GitRemoteHelper({
@ -13,46 +10,8 @@ GitRemoteHelper({
stdout: process.stdout,
api: {
init: async (p: ApiBaseParams) => {
const url = new URL(p.remoteUrl)
let repoName
let git3Address
let chainId = url.port ? parseInt(url.port) : null
if (url.hostname.indexOf(".") < 0) {
if (url.hostname.startsWith("0x")) {
git3Address = url.hostname
repoName = url.pathname.slice(1)
} else {
// use Default git3Address
git3Address = null
repoName = url.hostname.startsWith("/")
? url.hostname.slice(1)
: url.hostname
}
} else {
let nsSuffix = url.hostname.split(".")[1] // Todo: support sub domain
let ns = nameServices[nsSuffix]
if (!ns) throw new Error("invalid name service")
// Todo: resolve name service
git3Address = null // ns parse address
chainId = chainId || ns.chainId
repoName = url.pathname.slice(1)
}
chainId = chainId || 3334
let sender = url.username || null
let storage
if (chainId == 3334) {
storage = new ETHStorage(repoName, chainId, {
git3Address,
sender,
})
} else {
storage = new SLIStorage(repoName, chainId, {
git3Address,
sender,
})
}
const protocol = parseGit3URI(p.remoteUrl)
const storage = new protocol.storageClass(protocol)
git = new Git(p, storage)
return
},

@ -1,234 +1,278 @@
import { mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, existsSync } from 'fs'
import { ethers } from 'ethers'
import { Command } from 'commander'
import bip39 from 'bip39'
import inquirer from 'inquirer'
import parse from 'parse-git-config'
import { importActions, generateActions } from './actions.js'
import abis from "../config/abis.js"
import { mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs"
import { ethers } from "ethers"
import { Command } from "commander"
import bip39 from "bip39"
import inquirer from "inquirer"
import { importActions, generateActions } from "./actions.js"
import network from "../config/evm-network.js"
import { getWallet, randomRPC } from "../common/wallet.js"
import { parseGit3URI } from "../common/git3-protocol.js"
import { TxManager } from "../common/tx-manager.js"
const program = new Command()
program.name("git3").description("git3 mangement tool").version("0.1.0")
program
.name('git3')
.description('git3 mangement tool')
.version('0.1.0')
program.command('generate')
.alias('gen')
.alias('new')
.description('generate a cryto wallet to use git3')
.action(() => {
inquirer.prompt(generateActions).then(answers => {
const { keyType, name } = answers
const walletType = keyType === 'private key' ? 'privateKey' : 'mnemonic'
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
if (readdirSync(keyPath).includes(name)) {
console.error(`wallet ${name} already exists`)
return
}
const mnemonic = bip39.generateMnemonic()
const wallet = keyType === 'private key'
? ethers.Wallet.createRandom()
: ethers.Wallet.fromMnemonic(mnemonic)
const content = `${walletType}\n${keyType === 'private key' ? wallet.privateKey : mnemonic}\n`
writeFileSync(`${keyPath}/${name}`, content)
return
})
})
program.command('list', { isDefault: true })
.alias('ls')
.description('list all wallets in user folder ~/.git3/keys')
.option('-r, --raw', 'output raw wallet data with pravate key / mnemonic')
.action(params => {
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const wallets = readdirSync(keyPath)
if (wallets.length === 0) {
console.log('No wallet found, you can generate one use <git3 new>')
}
wallets.forEach(file => {
const content = readFileSync(`${keyPath}/${file}`).toString()
if (params.raw) {
console.log(`[${file}]`)
console.log(` ${content.split('\n')[0]} - ${content.split('\n')[1]}`)
console.log('\t')
return
}
console.log(`[${file}]`)
const [walletType, key] = content.split('\n')
const etherWallet = walletType === 'privateKey'
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
const address = etherWallet.address
console.log(`address: ${address}`)
console.log('\t')
})
})
program.command('import')
.description('import a wallet from a private key or mnemonic')
.action(() => {
inquirer.prompt(importActions).then(answers => {
const { keyType, key, name } = answers
const walletType = keyType === 'private key' ? 'privateKey' : 'mnemonic'
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
if (readdirSync(keyPath).includes(name)) {
console.error(`wallet ${name} already exists`)
return
}
const content = `${walletType}\n${key}\n`
writeFileSync(`${keyPath}/${name}`, content)
return
})
})
program.command('delete')
.description('delete a wallet')
.action(() => {
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const wallets = readdirSync(keyPath)
if (wallets.length === 0) {
console.error('No wallet found, you can generate one with `git3 generate`')
return
}
inquirer.prompt([
{
type: 'list',
name: 'wallet',
message: 'Select wallet to delete',
choices: wallets
}
]).then(answers => {
const { wallet } = answers
rmSync(`${keyPath}/${wallet}`)
.command("generate")
.alias("gen")
.alias("new")
.description("generate a cryto wallet to use git3")
.action(() => {
inquirer.prompt(generateActions).then((answers) => {
const { keyType, name } = answers
const walletType =
keyType === "private key" ? "privateKey" : "mnemonic"
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
if (readdirSync(keyPath).includes(name)) {
console.error(`wallet ${name} already exists`)
return
}
const mnemonic = bip39.generateMnemonic()
const wallet =
keyType === "private key"
? ethers.Wallet.createRandom()
: ethers.Wallet.fromMnemonic(mnemonic)
const content = `${walletType}\n${
keyType === "private key" ? wallet.privateKey : mnemonic
}\n`
writeFileSync(`${keyPath}/${name}`, content)
return
})
})
})
program.command('create')
.argument('[wallet]', 'wallet to use', 'default')
.argument('[repo]', 'repo name to create')
.description('create a new repo')
.action((wallet, repo) => {
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const content = readFileSync(`${keyPath}/${wallet}`).toString()
const [walletType, key] = content.split('\n')
const provider = new ethers.providers.JsonRpcProvider('https://galileo.web3q.io:8545');
let etherWallet = walletType === 'privateKey'
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
etherWallet = etherWallet.connect(provider)
let net = network[3334]
const contract = new ethers.Contract(
net.contracts.git3,
abis.ETHStorage,
etherWallet)
program
.command("list", { isDefault: true })
.alias("ls")
.description("list all wallets in user folder ~/.git3/keys")
.option("-r, --raw", "output raw wallet data with pravate key / mnemonic")
.action((params) => {
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const wallets = readdirSync(keyPath)
if (wallets.length === 0) {
console.log("No wallet found, you can generate one use <git3 new>")
}
contract.repoNameToOwner(Buffer.from(repo))
.then((res: any) => { console.log(res) })
.catch((err: any) => { console.error(err) })
contract.createRepo(Buffer.from(repo))
.then((res: any) => { console.log(res) })
.catch((err: any) => { console.error(err) })
wallets.forEach((file) => {
const content = readFileSync(`${keyPath}/${file}`).toString()
if (params.raw) {
console.log(`[${file}]`)
console.log(
` ${content.split("\n")[0]} - ${content.split("\n")[1]}`
)
console.log("\t")
return
}
console.log(`[${file}]`)
const [walletType, key] = content.split("\n")
const etherWallet =
walletType === "privateKey"
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
const address = etherWallet.address
console.log(`address: ${address}`)
console.log("\t")
})
})
})
program
.command("import")
.description("import a wallet from a private key or mnemonic")
.action(() => {
inquirer.prompt(importActions).then((answers) => {
const { keyType, key, name } = answers
const walletType =
keyType === "private key" ? "privateKey" : "mnemonic"
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
if (readdirSync(keyPath).includes(name)) {
console.error(`wallet ${name} already exists`)
return
}
const content = `${walletType}\n${key}\n`
writeFileSync(`${keyPath}/${name}`, content)
return
})
})
program.command('info')
.argument('[wallet]', 'wallet you want to get info', 'default')
.description('get info of a wallet')
.action(wallet => {
program
.command("delete")
.description("delete a wallet")
.action(() => {
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const wallets = readdirSync(keyPath)
if (wallets.length === 0) {
console.error(
"No wallet found, you can generate one with `git3 generate`"
)
return
}
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
inquirer
.prompt([
{
type: "list",
name: "wallet",
message: "Select wallet to delete",
choices: wallets,
},
])
.then((answers) => {
const { wallet } = answers
rmSync(`${keyPath}/${wallet}`)
})
})
const content = readFileSync(`${keyPath}/${wallet}`).toString()
const [walletType, key] = content.split('\n')
const provider = new ethers.providers.JsonRpcProvider('https://galileo.web3q.io:8545');
program
.command("create")
.argument("<uri>", "ex: default@git3.w3q/repo_name")
.description("create a new repo")
.action(async (uri) => {
if (!uri.startsWith("git3://")) {
uri = "git3://" + uri
}
const protocol = parseGit3URI(uri)
let owner = await protocol.contract.repoNameToOwner(
Buffer.from(protocol.repoName)
)
if (owner != "0x0000000000000000000000000000000000000000") {
console.error(`repo ${protocol.repoName} already exists`)
return
}
console.log(
`creating repo ${protocol.repoName} on ${protocol.netConfig.name}...`
)
const txManager = new TxManager(
protocol.contract,
protocol.chainId,
protocol.netConfig.txConst
)
let receipt = await txManager.SendCall("createRepo", [
Buffer.from(protocol.repoName),
])
if (
protocol.netConfig.explorers &&
protocol.netConfig.explorers.length > 0
) {
console.log(
protocol.netConfig.explorers[0].url.replace(/\/+$/, "") +
"/tx/" +
receipt.transactionHash
)
} else {
console.log(receipt.transactionHash)
}
console.log(`repo ${protocol.repoName} created.`)
})
let etherWallet = walletType === 'privateKey'
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
program
.command("info")
.argument("[wallet]", "wallet you want to get info", "default")
.description("get info of a wallet")
.action((wallet) => {
let etherWallet = getWallet(wallet)
etherWallet = etherWallet.connect(provider)
const address = etherWallet.address
const address = etherWallet.address
etherWallet.getBalance()
.then(balance => {
console.log(`wallet: ${wallet}`)
console.log(`address: ${address}`)
console.log(`balance: ${ethers.utils.formatUnits(balance)} eth`)
})
.catch(err => {
console.error(err)
return
})
})
program.command('set-wallet')
.alias('set')
.argument('<git3>', 'git3 remote')
.argument('[wallet]', 'wallet you want to bind', 'default')
.description('bind git3 remotes with a wallet')
.action((git3, wallet) => {
const currentConfig = parse.sync()
const existingRemote = currentConfig[`remote "${git3}"`]
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
if (!existsSync(`${keyPath}/${wallet}`)) {
console.error(`wallet ${wallet} not found, use <git3 new> to generate one`)
return
}
if (existingRemote) {
const newConfig = {
...currentConfig,
[`remote "${git3}"`]: {
...existingRemote,
wallet
for (let [_, net] of Object.entries(network)) {
const provider = new ethers.providers.JsonRpcProvider(
randomRPC(net.rpc)
)
const balance = provider.getBalance(address)
balance.then((res) => {
console.log(
`[${net.name}] balance: ${ethers.utils.formatUnits(
res,
net.nativeCurrency.decimals
)} ${net.nativeCurrency.symbol}`
)
})
}
}
// console.log(newConfig)
// const writer = createWriteStream('config', 'w')
let newConfigText = ''
Object.keys(newConfig).forEach(key => {
newConfigText += `[${key}]\n`
Object.keys(newConfig[key]).forEach(subKey => {
newConfigText += `\t${subKey} = ${newConfig[key][subKey]}\n`
})
})
let path = parse.resolveConfigPath("global") || ""
writeFileSync(path, newConfigText)
} else {
console.error(`remote ${git3} not found`)
console.error('you can add a remote with `git remote add <name> <url>')
}
})
})
program
.command("clear")
.description("clear pending nonce")
.argument("<uri>", "ex: default@git3.w3q")
.argument("[num]", "number of pending nonce to clear", 1)
.action(async (uri,num) => {
if (!uri.startsWith("git3://")) {
uri = "git3://" + uri
}
const protocol = parseGit3URI(uri, { skipRepoName: true })
const txManager = new TxManager(
protocol.contract,
protocol.chainId,
protocol.netConfig.txConst
)
let nonce = await protocol.wallet.getTransactionCount()
console.log(`current nonce: ${nonce}`)
await txManager.clearPendingNonce(num)
})
// Todo: set-wallet temporarily useless
// program
// .command("set-wallet")
// .alias("set")
// .argument("<git3>", "git3 remote")
// .argument("[wallet]", "wallet you want to bind", "default")
// .description("bind git3 remotes with a wallet")
// .action((git3, wallet) => {
// const currentConfig = parse.sync()
// const existingRemote = currentConfig[`remote "${git3}"`]
// const keyPath = process.env.HOME + "/.git3/keys"
// mkdirSync(keyPath, { recursive: true })
// if (!existsSync(`${keyPath}/${wallet}`)) {
// console.error(
// `wallet ${wallet} not found, use <git3 new> to generate one`
// )
// return
// }
// if (existingRemote) {
// const newConfig = {
// ...currentConfig,
// [`remote "${git3}"`]: {
// ...existingRemote,
// wallet,
// },
// }
// // console.log(newConfig)
// // const writer = createWriteStream('config', 'w')
// let newConfigText = ""
// Object.keys(newConfig).forEach((key) => {
// newConfigText += `[${key}]\n`
// Object.keys(newConfig[key]).forEach((subKey) => {
// newConfigText += `\t${subKey} = ${newConfig[key][subKey]}\n`
// })
// })
// let path = parse.resolveConfigPath("global") || ""
// writeFileSync(path, newConfigText)
// } else {
// console.error(`remote ${git3} not found`)
// console.error(
// "you can add a remote with `git remote add <name> <url>"
// )
// }
// })
program.parse()

@ -1,34 +1,29 @@
import { Ref, Status, Storage } from "./storage.js"
import { getWallet } from "../wallet/index.js"
import { ethers, Signer } from "ethers"
import { NonceManager } from "@ethersproject/experimental"
import abis from "../config/abis.js"
import network from "../config/evm-network.js"
import { ethers } from "ethers"
import { TxManager } from "../common/tx-manager.js"
import { Git3Protocol } from "../common/git3-protocol.js"
export class ETHStorage implements Storage {
repoName: string
wallet: Signer
wallet: ethers.Signer
contract: ethers.Contract
provider: ethers.providers.JsonRpcProvider
constructor(repoName: string, chainId: number, options: { git3Address: string | null, sender: string | null }) {
let net = network[chainId]
if (!net) throw new Error("chainId not supported")
this.repoName = repoName
this.wallet = getWallet(options.sender)
txManager: TxManager
let rpc = net.rpc[Math.floor(Math.random() * net.rpc.length)] //random get rpc
this.provider = new ethers.providers.JsonRpcProvider(rpc)
this.wallet = this.wallet.connect(this.provider)
this.wallet = new NonceManager(this.wallet)
let repoAddress = options.git3Address || net.contracts.git3
this.contract = new ethers.Contract(repoAddress, abis.ETHStorage, this.wallet)
constructor(protocol: Git3Protocol) {
this.repoName = protocol.repoName
this.contract = protocol.contract
this.wallet = protocol.wallet
this.txManager = new TxManager(
this.contract,
protocol.chainId,
protocol.netConfig.txConst
)
}
async repoRoles(): Promise<string[]> {
let owner = await this.contract.repoNameToOwner(Buffer.from(this.repoName))
let owner = await this.contract.repoNameToOwner(
Buffer.from(this.repoName)
)
if (owner === ethers.constants.AddressZero) return []
return [owner]
}
@ -39,28 +34,31 @@ export class ETHStorage implements Storage {
}
async download(path: string): Promise<[Status, Buffer]> {
const res = await this.contract.download(Buffer.from(this.repoName), Buffer.from(path))
const buffer = Buffer.from(res[0].slice(2), 'hex')
console.error(`=== download file ${path} result ===`)
// console.error(buffer.toString('utf-8'))
const res = await this.contract.download(
Buffer.from(this.repoName),
Buffer.from(path)
)
const buffer = Buffer.from(res[0].slice(2), "hex")
console.error(`=== download file ${path} succeed ===`)
return [Status.SUCCEED, buffer]
}
async upload(path: string, file: Buffer): Promise<Status> {
try {
console.error(`=== uploading file ${path} ===`)
const tx = await this.contract.upload(Buffer.from(this.repoName), Buffer.from(path), file, { gasLimit: 6000000 })
console.error(`send tx done: ${tx.hash}`)
await new Promise(r => setTimeout(r, 3000))
await tx.wait(1)
console.error(`upload succeed: ${tx.hash}`)
await this.txManager.SendCall("upload", [
Buffer.from(this.repoName),
Buffer.from(path),
file,
])
console.error(`=== upload ${path} succeed ===`)
return Status.SUCCEED
}
catch (error: any) {
console.error(`upload failed ${error.reason ? error.reason : "CALL_EXCEPTION"} : ${path}`)
} catch (error: any) {
this.txManager.CancelAll()
console.error(`upload failed: ${error}`)
return Status.FAILED
}
}
remove(path: string): Promise<Status> {
@ -68,29 +66,40 @@ export class ETHStorage implements Storage {
}
async listRefs(): Promise<Ref[]> {
const res: string[][] = await this.contract.listRefs(Buffer.from(this.repoName))
let refs = res.map(i => ({
ref: Buffer.from(i[1].slice(2), "hex").toString("utf8").slice(this.repoName.length + 1),
sha: i[0].slice(2)
const res: string[][] = await this.contract.listRefs(
Buffer.from(this.repoName)
)
let refs = res.map((i) => ({
ref: Buffer.from(i[1].slice(2), "hex")
.toString("utf8")
.slice(this.repoName.length + 1),
sha: i[0].slice(2),
}))
return refs
}
async setRef(path: string, sha: string): Promise<Status> {
try {
let tx = await this.contract.setRef(Buffer.from(this.repoName), Buffer.from(path), '0x' + sha, { gasLimit: 6000000 })
await new Promise(r => setTimeout(r, 1000))
await tx.wait(1)
}
catch (error: any) {
console.error(`ref set failed ${error.reason ? error.reason : "CALL_EXCEPTION"} : ${path}`)
console.error(`=== setting ref ${path} ===`)
await this.txManager.SendCall("setRef", [
Buffer.from(this.repoName),
Buffer.from(path),
"0x" + sha,
])
console.error(`ref set succeed ${path}`)
return Status.SUCCEED
} catch (error: any) {
console.error(`ref set failed ${error} : ${path}`)
return Status.FAILED
}
return Status.SUCCEED
}
async removeRef(path: string): Promise<Status> {
await this.contract.delRef(Buffer.from(this.repoName), Buffer.from(path))
await this.contract.delRef(
Buffer.from(this.repoName),
Buffer.from(path)
)
return Status.SUCCEED
}
}

@ -1,49 +1,35 @@
import { Ref, Status, Storage } from "./storage.js"
import { getWallet } from "../wallet/index.js"
import { TxManager } from "../wallet/tx-manager.js"
import { ethers, Signer } from "ethers"
import { NonceManager } from "@ethersproject/experimental"
import abis from "../config/abis.js"
import network from "../config/evm-network.js"
import { TxManager } from "../common/tx-manager.js"
import { ethers } from "ethers"
import ipfsConf from "../config/ipfs.js"
import axios from "axios"
import { Git3Protocol } from "../common/git3-protocol.js"
export class SLIStorage implements Storage {
repoName: string
wallet: Signer
wallet: ethers.Wallet
contract: ethers.Contract
provider: ethers.providers.JsonRpcProvider
auth: string
txManager: TxManager
constructor(
repoName: string,
chainId: number,
options: { git3Address: string | null; sender: string | null }
) {
let net = network[chainId]
if (!net) throw new Error("chainId not supported")
this.repoName = repoName
this.wallet = getWallet(options.sender)
let rpc = net.rpc[Math.floor(Math.random() * net.rpc.length)] //random get rpc
this.provider = new ethers.providers.JsonRpcProvider(rpc)
this.wallet = this.wallet.connect(this.provider)
// this.wallet = new NonceManager(this.wallet)
auth: string
let repoAddress = options.git3Address || net.contracts.git3
this.contract = new ethers.Contract(
repoAddress,
abis.SLIStorage,
this.wallet
constructor(protocol: Git3Protocol) {
this.repoName = protocol.repoName
this.contract = protocol.contract
this.wallet = protocol.wallet
this.txManager = new TxManager(
this.contract,
protocol.chainId,
protocol.netConfig.txConst
)
this.auth =
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweGFEQTdCOWFlQTdGNTc2ZDI5NzM0ZWUxY0Q2ODVFMzc2OWNCM2QwRDEiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY3NTQ5NDYwMDkzMiwibmFtZSI6ImZ2bS1oYWNrc29uIn0.YBqfsj_LTZSJPKc0OH586avnQNqove_Htzl5rrToXTk"
this.txManager = new TxManager(this.contract, chainId, net.txConst)
this.txManager = new TxManager(
this.contract,
protocol.chainId,
protocol.netConfig.txConst
)
}
async repoRoles(): Promise<string[]> {
@ -98,7 +84,7 @@ export class SLIStorage implements Storage {
Buffer.from(path),
Buffer.from(cid),
])
console.error(`=== upload ${path} ${cid} succeed ===`)
console.error(`=== upload ${path} ${cid.slice(0, 6)} succeed ===`)
return Status.SUCCEED
} catch (error: any) {
@ -150,9 +136,10 @@ export class SLIStorage implements Storage {
}
async storeIPFS(data: Buffer): Promise<string> {
const RETRY_TIMES = 10
const TIMEOUT = 30
let response
for (let i = 0; i < 10; i++) {
// Todo: add timeout
for (let i = 0; i < RETRY_TIMES; i++) {
try {
response = await axios.post(
"https://api.nft.storage/upload",
@ -162,6 +149,7 @@ export class SLIStorage implements Storage {
"Content-Type": "application/octet-stream",
Authorization: this.auth,
},
timeout: TIMEOUT * 1000,
}
)
if (response.status == 200) {

@ -1,19 +0,0 @@
import { mkdirSync, readFileSync } from "fs"
import { ethers } from 'ethers'
export function getWallet(sender: string | null): ethers.Wallet {
// Todo: according sender address to select wallet, if sender==null then use default wallet
const wallet = 'default'
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
const content = readFileSync(`${keyPath}/${wallet}`).toString()
const [walletType, key] = content.split('\n')
let etherWallet = walletType === 'privateKey'
? new ethers.Wallet(key)
: ethers.Wallet.fromMnemonic(key)
return etherWallet
}
Loading…
Cancel
Save