brainchain-worker 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.js +4 -5
- package/dist/exit.js +33 -62
- package/dist/network.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,9 +228,9 @@ Earnings come back the same way:
|
|
|
228
228
|
BRAINCHAIN_NETWORK=mainnet npx brainchain-worker exit --to-solana <your Solana address>
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
-
`exit` sends the worker's USDG on BrainChain to the desk, which pays it
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
`exit` sends the worker's USDG on BrainChain to the desk, which pays it back within a minute, less a 0.5% fee, instead
|
|
232
|
+
of the bridge's seven-day withdrawal. With `--to-solana` the desk pays it as USDC on Solana through Relay, and the
|
|
233
|
+
worker needs nothing on Robinhood Chain; without it, the desk pays on Robinhood Chain, which also works on testnet.
|
|
234
234
|
|
|
235
235
|
Every Relay move is priced before anything is signed: one that would cost more than 3% of its value, or 0.50 USD on
|
|
236
236
|
a small move, stops the command and nothing is sent.
|
package/dist/cli.js
CHANGED
|
@@ -76,11 +76,10 @@ const COMMAND_USAGE = {
|
|
|
76
76
|
minute. --eth works on both networks, the tokens on mainnet. --dry
|
|
77
77
|
checks the balances and sends nothing.`,
|
|
78
78
|
exit: ` brainchain-worker exit [--amount <USDG>] [--to-solana <address>] [--dry]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ETH on Robinhood Chain for gas). --dry checks and sends nothing.`,
|
|
79
|
+
Get the worker's USDG on BrainChain back within a minute, not the
|
|
80
|
+
bridge's seven days: the desk pays it on Robinhood Chain less a 0.5%
|
|
81
|
+
fee, or as USDC on Solana with --to-solana (mainnet). Without --amount,
|
|
82
|
+
the whole balance. --dry checks and sends nothing.`,
|
|
84
83
|
fund: ` brainchain-worker fund --from-solana --usdc <amount> [--stake] [--gas-usdc <amount>] [--dry]
|
|
85
84
|
Fund the worker from a Solana wallet: Relay turns its USDC into ETH
|
|
86
85
|
for gas (2 USDC by default) and, with --stake, the rest into the stake
|
package/dist/exit.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
// `exit`: the worker's USDG on BrainChain back
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
import
|
|
1
|
+
// `exit`: the worker's USDG on BrainChain paid back within a minute instead of the bridge's seven days. The desk pays
|
|
2
|
+
// it on Robinhood Chain, or, with `--to-solana`, as USDC on Solana through Relay: the transfer to the desk carries the
|
|
3
|
+
// Solana address after its arguments, so nothing else is signed and nothing is needed on Robinhood Chain.
|
|
4
|
+
import bs58 from "bs58";
|
|
5
|
+
import { createPublicClient, createWalletClient, encodeFunctionData, erc20Abi, formatUnits, http, parseUnits } from "viem";
|
|
5
6
|
import { RECEIPT_MS } from "./chain.js";
|
|
6
|
-
import { chainOf
|
|
7
|
-
|
|
8
|
-
/** A Solana address: 32 to 44 base58 characters. */
|
|
7
|
+
import { chainOf } from "./network.js";
|
|
8
|
+
/** A Solana address: 32 bytes in base58. */
|
|
9
9
|
export function isSolanaAddress(value) {
|
|
10
|
-
|
|
10
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value))
|
|
11
|
+
return false;
|
|
12
|
+
try {
|
|
13
|
+
return bs58.decode(value).length === 32;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** A transfer to the desk with the Solana address after its arguments, which the desk reads to pay there. */
|
|
20
|
+
export function exitCalldata(desk, amount, solana) {
|
|
21
|
+
const transfer = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [desk, amount] });
|
|
22
|
+
return solana ? `${transfer}${Buffer.from(bs58.decode(solana)).toString("hex")}` : transfer;
|
|
11
23
|
}
|
|
12
24
|
export function exitFee(amount, terms) {
|
|
13
25
|
const proportional = (amount * BigInt(terms.feeBps)) / 10000n;
|
|
@@ -32,35 +44,20 @@ export async function exitRecord(service, tx, fetchImpl = fetch) {
|
|
|
32
44
|
return (await response.json()).exits[0];
|
|
33
45
|
}
|
|
34
46
|
export function createExitChain(network, account) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const l3Wallet = createWalletClient({ account, chain: chainOf(network), transport: http(network.rpc, { timeout: 20_000 }) });
|
|
38
|
-
const parent = bridge ? createPublicClient({ chain: parentChainOf(bridge), transport: http(bridge.rpc, { timeout: 20_000 }) }) : undefined;
|
|
39
|
-
const parentWallet = bridge ? createWalletClient({ account, chain: parentChainOf(bridge), transport: http(bridge.rpc, { timeout: 20_000 }) }) : undefined;
|
|
47
|
+
const read = createPublicClient({ chain: chainOf(network), transport: http(network.rpc, { timeout: 20_000 }) });
|
|
48
|
+
const wallet = createWalletClient({ account, chain: chainOf(network), transport: http(network.rpc, { timeout: 20_000 }) });
|
|
40
49
|
return {
|
|
41
50
|
account: account.address,
|
|
42
|
-
bountyBalance: (token) =>
|
|
43
|
-
async sendToDesk(token, desk, amount) {
|
|
44
|
-
const hash = await
|
|
45
|
-
const receipt = await
|
|
51
|
+
bountyBalance: (token) => read.readContract({ address: token, abi: erc20Abi, functionName: "balanceOf", args: [account.address] }),
|
|
52
|
+
async sendToDesk(token, desk, amount, solana) {
|
|
53
|
+
const hash = await wallet.sendTransaction({ to: token, data: exitCalldata(desk, amount, solana) });
|
|
54
|
+
const receipt = await read.waitForTransactionReceipt({ hash, timeout: RECEIPT_MS });
|
|
46
55
|
if (receipt.status !== "success")
|
|
47
56
|
throw new Error(`the transfer to the desk reverted (${hash})`);
|
|
48
57
|
return hash;
|
|
49
58
|
},
|
|
50
|
-
parentEth: () => (parent ? parent.getBalance({ address: account.address }) : Promise.resolve(0n)),
|
|
51
|
-
async sendParent(item) {
|
|
52
|
-
if (!parent || !parentWallet)
|
|
53
|
-
throw new Error(`${network.name} has no parent chain`);
|
|
54
|
-
const hash = await parentWallet.sendTransaction({ to: item.to, data: item.data, value: BigInt(item.value ?? "0") });
|
|
55
|
-
const receipt = await parent.waitForTransactionReceipt({ hash, timeout: RECEIPT_MS });
|
|
56
|
-
if (receipt.status !== "success")
|
|
57
|
-
throw new Error(`Relay's transaction ${hash} reverted`);
|
|
58
|
-
return hash;
|
|
59
|
-
},
|
|
60
59
|
};
|
|
61
60
|
}
|
|
62
|
-
/** Gas the two Relay transactions on Robinhood Chain need, with room to spare. */
|
|
63
|
-
export const RELAY_GAS_WEI = 20000000000000n;
|
|
64
61
|
export async function exit(network, chain, options) {
|
|
65
62
|
const { log, dry } = options;
|
|
66
63
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -89,25 +86,14 @@ export async function exit(network, chain, options) {
|
|
|
89
86
|
const fee = exitFee(amount, terms);
|
|
90
87
|
const net = amount - fee;
|
|
91
88
|
log(`Exit ${show(amount)} from ${network.name}: ${show(net)} arrives on the parent chain (chain ${terms.payoutChainId}) after a ${show(fee)} fee.`);
|
|
92
|
-
if (options.toSolana !== undefined)
|
|
93
|
-
|
|
94
|
-
if (eth < RELAY_GAS_WEI) {
|
|
95
|
-
throw new Error(`Moving it on to Solana needs about 0.00002 ETH on ${network.bridge?.name ?? "the parent chain"} for gas; the wallet holds ${formatUnits(eth, 18)}. \`fund --from-solana\` brings some.`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const solanaQuote = (net) => (options.quote ?? relayQuote)({ user: chain.account, recipient: options.toSolana, originChainId: terms.payoutChainId, destinationChainId: SOLANA_CHAIN_ID, originCurrency: terms.payoutToken, destinationCurrency: SOLANA_USDC, amount: net }, fetchImpl);
|
|
99
|
-
// Priced before anything leaves BrainChain, so a refused Solana leg never strands the exit halfway.
|
|
100
|
-
if (options.toSolana !== undefined) {
|
|
101
|
-
const preview = await solanaQuote(net);
|
|
102
|
-
checkReturn(preview);
|
|
103
|
-
log(`Relay quotes ${preview.outFormatted} USDC on Solana for it.`);
|
|
104
|
-
}
|
|
89
|
+
if (options.toSolana !== undefined)
|
|
90
|
+
log(`Paid as USDC on Solana to ${options.toSolana}, less Relay's own fee, unless the move would cost more than 3% (or 0.50 USD); then it is paid on the parent chain.`);
|
|
105
91
|
if (dry) {
|
|
106
92
|
log("Dry run, nothing sent.");
|
|
107
93
|
return;
|
|
108
94
|
}
|
|
109
|
-
const tx = await chain.sendToDesk(terms.token, terms.desk, amount);
|
|
110
|
-
log(`Sent to the desk on ${network.name} (${tx}). Waiting for the payout
|
|
95
|
+
const tx = await chain.sendToDesk(terms.token, terms.desk, amount, options.toSolana);
|
|
96
|
+
log(`Sent to the desk on ${network.name} (${tx}). Waiting for the payout, usually under a minute.`);
|
|
111
97
|
const end = Date.now() + (options.waitMs ?? 1_800_000);
|
|
112
98
|
let last = "";
|
|
113
99
|
let record;
|
|
@@ -129,24 +115,9 @@ export async function exit(network, chain, options) {
|
|
|
129
115
|
process.exitCode = 1;
|
|
130
116
|
return;
|
|
131
117
|
}
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
if (record.solana && record.relayRequest) {
|
|
119
|
+
log(`Paid on Solana: ${show(BigInt(record.net))} as USDC to ${record.solana} (Relay request ${record.relayRequest}).`);
|
|
134
120
|
return;
|
|
135
|
-
const quote = await solanaQuote(BigInt(record.net));
|
|
136
|
-
checkReturn(quote);
|
|
137
|
-
await sendEvmSteps(quote, chain);
|
|
138
|
-
log(`Moving ${show(BigInt(record.net))} to Solana: ${quote.outFormatted} USDC to ${options.toSolana}.`);
|
|
139
|
-
const status = await waitForRelay(quote.requestId, 600_000, fetchImpl);
|
|
140
|
-
if (status === "success")
|
|
141
|
-
log(`Arrived on Solana. Relay request ${quote.requestId}.`);
|
|
142
|
-
else {
|
|
143
|
-
log(`Relay reports "${status}" for request ${quote.requestId}. A refund comes back to the wallet on the parent chain.`);
|
|
144
|
-
process.exitCode = 1;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function sendEvmSteps(quote, chain) {
|
|
148
|
-
for (const step of quote.steps) {
|
|
149
|
-
for (const item of step.items)
|
|
150
|
-
await chain.sendParent(item.data);
|
|
151
121
|
}
|
|
122
|
+
log(`Paid on the parent chain: ${show(BigInt(record.net))} (${record.payoutTx})${record.reason ? `. ${record.reason}` : ""}.`);
|
|
152
123
|
}
|
package/dist/network.js
CHANGED
|
@@ -78,11 +78,12 @@ export const MAINNET = {
|
|
|
78
78
|
weth: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
|
|
79
79
|
gas: "Send it a little ETH there for gas",
|
|
80
80
|
income: "0xeb1b4168D4b87D3d9F0D64E13B7071251BBB63b7",
|
|
81
|
-
// Shipped's registry and StaticCallCheck
|
|
81
|
+
// Shipped's registry and StaticCallCheck, and BrainChain's ProofIndex the check reads.
|
|
82
82
|
bonds: {
|
|
83
83
|
registry: "0xD5Da523ee49CB7Fe855B0C6fa38707190A9D531F",
|
|
84
84
|
check: "0x2e7C4F0F2e6079C3B5F7Cd2440308234CBDc537D",
|
|
85
85
|
usdg: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
|
|
86
|
+
proofIndex: "0x0fb02559e704549794EdF3FBBBe91DB88bB849ee",
|
|
86
87
|
},
|
|
87
88
|
},
|
|
88
89
|
identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainchain-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Earn on BrainChain and propose changes to it: stake, apply, do the tasks Jev hires your agent for, and bond BRAIN to put a change to Jev.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "BrainChain <hello@usebrainchain.com>",
|