@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-other-dev-d15096d.0.d15096d

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.
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @import {Command} from 'commander';
3
+ * @import {File} from './util/file.js';
4
+ * @import * as ConfigHelpers from './config.js';
5
+ */
6
+ /**
7
+ *
8
+ * @param {Command} program
9
+ * @param {ConfigHelpers} configHelpers
10
+ * @param {() => File} makeConfigFile
11
+ */
12
+ export const addConfigCommands = (program, configHelpers, makeConfigFile) => {
13
+ const config = program.command('config').description('Manage config');
14
+
15
+ config
16
+ .command('show')
17
+ .description('Show current config')
18
+ .action(async () => {
19
+ await configHelpers.show(makeConfigFile());
20
+ });
21
+
22
+ config
23
+ .command('init')
24
+ .description('Set initial config values')
25
+ .requiredOption(
26
+ '--noble-seed <seed>',
27
+ 'Seed phrase for Noble account. CAUTION: Stored unencrypted in file system',
28
+ )
29
+ .requiredOption(
30
+ '--eth-seed <seed>',
31
+ 'Seed phrase for Ethereum account. CAUTION: Stored unencrypted in file system',
32
+ )
33
+ .requiredOption(
34
+ '--agoric-seed <seed>',
35
+ 'Seed phrase for Agoric LP account. CAUTION: Stored unencrypted in file system',
36
+ )
37
+ .option(
38
+ '--agoric-rpc [url]',
39
+ 'Agoric RPC endpoint',
40
+ 'http://127.0.0.1:26656',
41
+ )
42
+ .option(
43
+ '--agoric-api [url]',
44
+ 'Agoric RPC endpoint',
45
+ 'http://127.0.0.1:1317',
46
+ )
47
+ .option('--noble-rpc [url]', 'Noble RPC endpoint', 'http://127.0.0.1:26657')
48
+ .option('--noble-api [url]', 'Noble API endpoint', 'http://127.0.0.1:1318')
49
+ .option('--eth-rpc [url]', 'Ethereum RPC Endpoint', 'http://127.0.0.1:8545')
50
+ .option(
51
+ '--noble-to-agoric-channel [channel]',
52
+ 'Channel ID on Noble for Agoric',
53
+ 'channel-21',
54
+ )
55
+ .option(
56
+ '--token-messenger-address [address]',
57
+ 'Address of TokenMessenger contract',
58
+ // Default to ETH mainnet contract address. For ETH sepolia, use 0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5
59
+ '0xbd3fa81b58ba92a82136038b25adec7066af3155',
60
+ )
61
+ .option(
62
+ '--token-contract-address [address]',
63
+ 'Address of USDC token contract',
64
+ // Detault to ETH mainnet token address. For ETH sepolia, use 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
65
+ '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
66
+ )
67
+ .action(async options => {
68
+ await configHelpers.init(makeConfigFile(), options);
69
+ });
70
+
71
+ config
72
+ .command('update')
73
+ .description('Update config values')
74
+ .option(
75
+ '--noble-seed [string]',
76
+ 'Seed phrase for Noble account. CAUTION: Stored unencrypted in file system',
77
+ )
78
+ .option(
79
+ '--eth-seed [string]',
80
+ 'Seed phrase for Ethereum account. CAUTION: Stored unencrypted in file system',
81
+ )
82
+ .option(
83
+ '--agoric-seed <seed>',
84
+ 'Seed phrase for Agoric LP account. CAUTION: Stored unencrypted in file system',
85
+ )
86
+ .option('--agoric-rpc [url]', 'Agoric RPC endpoint')
87
+ .option('--agoric-api [url]', 'Agoric API endpoint')
88
+ .option('--noble-rpc [url]', 'Noble RPC endpoint')
89
+ .option('--noble-api [url]', 'Noble API endpoint')
90
+ .option('--eth-rpc [url]', 'Ethereum RPC Endpoint')
91
+ .option(
92
+ '--noble-to-agoric-channel [channel]',
93
+ 'Channel ID on Noble for Agoric',
94
+ )
95
+ .option(
96
+ '--token-messenger-address [address]',
97
+ 'Address of TokenMessenger contract',
98
+ )
99
+ .option(
100
+ '--token-contract-address [address]',
101
+ 'Address of USDC token contract',
102
+ )
103
+ .action(async options => {
104
+ await configHelpers.update(makeConfigFile(), options);
105
+ });
106
+
107
+ return config;
108
+ };
package/src/cli/config.js CHANGED
@@ -1,24 +1,32 @@
1
1
  import * as readline from 'node:readline/promises';
2
2
  import { stdin as input, stdout as output } from 'node:process';
3
3
 
4
+ /**
5
+ @typedef {{
6
+ bech32Prefix: string,
7
+ api: string,
8
+ USDCDenom: string
9
+ }} DestinationChain
10
+ */
11
+
4
12
  /**
5
13
  @typedef {{
6
14
  nobleSeed: string,
7
15
  ethSeed: string,
8
16
  nobleToAgoricChannel: string,
9
- agoricRpc: string,
10
17
  nobleApi: string,
11
18
  nobleRpc: string,
12
19
  ethRpc: string,
13
20
  tokenMessengerAddress: string,
14
21
  tokenAddress: string
22
+ destinationChains?: DestinationChain[]
15
23
  }} ConfigOpts
16
24
  */
17
25
 
18
- /** @import { file } from '../util/file' */
26
+ /** @import { File } from './util/file.js' */
19
27
 
20
- const init = async (
21
- /** @type {file} */ configFile,
28
+ export const init = async (
29
+ /** @type {File} */ configFile,
22
30
  /** @type {ConfigOpts} */ options,
23
31
  out = console,
24
32
  rl = readline.createInterface({ input, output }),
@@ -52,8 +60,8 @@ const init = async (
52
60
  await writeConfig();
53
61
  };
54
62
 
55
- const update = async (
56
- /** @type {file} */ configFile,
63
+ export const update = async (
64
+ /** @type {File} */ configFile,
57
65
  /** @type {Partial<ConfigOpts>} */ options,
58
66
  out = console,
59
67
  ) => {
@@ -83,7 +91,7 @@ const update = async (
83
91
  await updateConfig({ ...JSON.parse(file), ...options });
84
92
  };
85
93
 
86
- const show = async (/** @type {file} */ configFile, out = console) => {
94
+ export const show = async (/** @type {File} */ configFile, out = console) => {
87
95
  let contents;
88
96
  await null;
89
97
  try {
@@ -97,5 +105,3 @@ const show = async (/** @type {file} */ configFile, out = console) => {
97
105
  out.log(`Config found at ${configFile.path}:`);
98
106
  out.log(contents);
99
107
  };
100
-
101
- export default { init, update, show };
@@ -0,0 +1,161 @@
1
+ /* eslint-env node */
2
+ /**
3
+ * @import {Command} from 'commander';
4
+ * @import {Amount, Brand} from '@agoric/ertp';
5
+ * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js';
6
+ * @import {ExecuteOfferAction} from '@agoric/smart-wallet/src/smartWallet.js';
7
+ * @import {USDCProposalShapes} from '../pool-share-math.js';
8
+ * @import {SmartWalletKit} from '@agoric/client-utils';
9
+ */
10
+
11
+ import {
12
+ fetchEnvNetworkConfig,
13
+ makeSmartWalletKit,
14
+ } from '@agoric/client-utils';
15
+ import { AmountMath } from '@agoric/ertp';
16
+ import {
17
+ assertParsableNumber,
18
+ ceilDivideBy,
19
+ floorDivideBy,
20
+ multiplyBy,
21
+ parseRatio,
22
+ } from '@agoric/ertp/src/ratio.js';
23
+ import { InvalidArgumentError } from 'commander';
24
+ import { outputActionAndHint } from './bridge-action.js';
25
+ import { Offers } from '../clientSupport.js';
26
+
27
+ export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
28
+
29
+ /** @param {string} arg */
30
+ const parseDecimal = arg => {
31
+ try {
32
+ assertParsableNumber(arg);
33
+ const n = Number(arg);
34
+ return n;
35
+ } catch {
36
+ throw new InvalidArgumentError('Not a number');
37
+ }
38
+ };
39
+
40
+ /**
41
+ * @param {string} amountString
42
+ * @param {Brand<'nat'>} usdc
43
+ */
44
+ const parseUSDCAmount = (amountString, usdc) => {
45
+ const USDC_DECIMALS = 6;
46
+ const unit = AmountMath.make(usdc, 10n ** BigInt(USDC_DECIMALS));
47
+ return multiplyBy(unit, parseRatio(amountString, usdc));
48
+ };
49
+
50
+ /**
51
+ * @param {Command} program
52
+ * @param {{
53
+ * fetch?: Window['fetch'];
54
+ * smartWalletKit?: SmartWalletKit;
55
+ * stdout: typeof process.stdout;
56
+ * stderr: typeof process.stderr;
57
+ * env: typeof process.env;
58
+ * now: typeof Date.now;
59
+ * }} io
60
+ */
61
+ export const addLPCommands = (
62
+ program,
63
+ { fetch, smartWalletKit, stderr, stdout, env, now },
64
+ ) => {
65
+ const loadSwk = async () => {
66
+ if (smartWalletKit) {
67
+ return smartWalletKit;
68
+ }
69
+ assert(fetch);
70
+ const networkConfig = await fetchEnvNetworkConfig({ env, fetch });
71
+ return makeSmartWalletKit({ delay, fetch }, networkConfig);
72
+ };
73
+ /** @type {undefined | ReturnType<typeof loadSwk>} */
74
+ let swkP;
75
+
76
+ program
77
+ .command('deposit')
78
+ .description('Deposit USDC into pool')
79
+ .addHelpText(
80
+ 'after',
81
+ '\nPipe the STDOUT to a file such as deposit.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer deposit.json --from gov1 --keyring-backend="test"',
82
+ )
83
+ .requiredOption('--amount <number>', 'USDC amount', parseDecimal)
84
+ .option('--offerId <string>', 'Offer id', String, `lpDeposit-${now()}`)
85
+ .action(async opts => {
86
+ swkP ||= loadSwk();
87
+ const swk = await swkP;
88
+
89
+ /** @type {Brand<'nat'>} */
90
+ // @ts-expect-error it doesnt recognize usdc as a Brand type
91
+ const usdc = swk.agoricNames.brand.USDC;
92
+ assert(usdc, 'USDC brand not in agoricNames');
93
+
94
+ /** @type {Brand<'nat'>} */
95
+ // @ts-expect-error it doesnt recognize FastLP as a Brand type
96
+ const poolShare = swk.agoricNames.brand.FastLP;
97
+ assert(poolShare, 'FastLP brand not in agoricNames');
98
+
99
+ const usdcAmount = parseUSDCAmount(opts.amount, usdc);
100
+
101
+ const metrics = await swk.readPublished('fastUsdc.poolMetrics');
102
+ const fastLPAmount = floorDivideBy(usdcAmount, metrics.shareWorth);
103
+
104
+ const offer = Offers.fastUsdc.Deposit(swk.agoricNames, {
105
+ offerId: opts.offerId,
106
+ fastLPAmount: fastLPAmount.value,
107
+ usdcAmount: usdcAmount.value,
108
+ });
109
+
110
+ /** @type {ExecuteOfferAction} */
111
+ const bridgeAction = {
112
+ method: 'executeOffer',
113
+ offer,
114
+ };
115
+
116
+ outputActionAndHint(bridgeAction, { stderr, stdout }, swk.marshaller);
117
+ });
118
+
119
+ program
120
+ .command('withdraw')
121
+ .description("Withdraw USDC from the LP's pool share")
122
+ .addHelpText(
123
+ 'after',
124
+ '\nPipe the STDOUT to a file such as withdraw.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer withdraw.json --from gov1 --keyring-backend="test"',
125
+ )
126
+ .requiredOption('--amount <number>', 'USDC amount', parseDecimal)
127
+ .option('--offerId <string>', 'Offer id', String, `lpWithdraw-${now()}`)
128
+ .action(async opts => {
129
+ swkP ||= loadSwk();
130
+ const swk = await swkP;
131
+
132
+ /** @type {Brand<'nat'>} */
133
+ // @ts-expect-error it doesnt recognize FastLP as a Brand type
134
+ const poolShare = swk.agoricNames.brand.FastLP;
135
+ assert(poolShare, 'FastLP brand not in agoricNames');
136
+
137
+ /** @type {Brand<'nat'>} */
138
+ // @ts-expect-error it doesnt recognize usdc as a Brand type
139
+ const usdc = swk.agoricNames.brand.USDC;
140
+ assert(usdc, 'USDC brand not in agoricNames');
141
+
142
+ const usdcAmount = parseUSDCAmount(opts.amount, usdc);
143
+
144
+ const metrics = await swk.readPublished('fastUsdc.poolMetrics');
145
+ const fastLPAmount = ceilDivideBy(usdcAmount, metrics.shareWorth);
146
+
147
+ const offer = Offers.fastUsdc.Withdraw(swk.agoricNames, {
148
+ offerId: opts.offerId,
149
+ fastLPAmount: fastLPAmount.value,
150
+ usdcAmount: usdcAmount.value,
151
+ });
152
+
153
+ outputActionAndHint(
154
+ { method: 'executeOffer', offer },
155
+ { stderr, stdout },
156
+ swk.marshaller,
157
+ );
158
+ });
159
+
160
+ return program;
161
+ };
@@ -0,0 +1,143 @@
1
+ /* eslint-env node */
2
+ /**
3
+ * @import {Command} from 'commander';
4
+ * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js';
5
+ * @import {ExecuteOfferAction} from '@agoric/smart-wallet/src/smartWallet.js';
6
+ */
7
+
8
+ import {
9
+ fetchEnvNetworkConfig,
10
+ makeSmartWalletKit,
11
+ } from '@agoric/client-utils';
12
+ import { mustMatch } from '@agoric/internal';
13
+ import { Nat } from '@endo/nat';
14
+ import { InvalidArgumentError } from 'commander';
15
+ import { INVITATION_MAKERS_DESC } from '../operator-kit-interface.js';
16
+ import { CctpTxEvidenceShape } from '../type-guards.js';
17
+ import { outputActionAndHint } from './bridge-action.js';
18
+
19
+ export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
20
+
21
+ /** @param {string} arg */
22
+ const parseNat = arg => {
23
+ const n = Nat(BigInt(arg));
24
+ return n;
25
+ };
26
+
27
+ /** @param {string} arg */
28
+ const parseHex = arg => {
29
+ if (!arg.startsWith('0x')) throw new InvalidArgumentError('not a hex string');
30
+ return arg;
31
+ };
32
+
33
+ /**
34
+ * @param {Command} program
35
+ * @param {{
36
+ * fetch: Window['fetch'];
37
+ * stdout: typeof process.stdout;
38
+ * stderr: typeof process.stderr;
39
+ * env: typeof process.env;
40
+ * now: typeof Date.now;
41
+ * }} io
42
+ */
43
+ export const addOperatorCommands = (
44
+ program,
45
+ { fetch, stderr, stdout, env, now },
46
+ ) => {
47
+ const operator = program
48
+ .command('operator')
49
+ .description('Oracle operator commands');
50
+
51
+ operator
52
+ .command('accept')
53
+ .description('Accept invitation to be an operator')
54
+ .addHelpText(
55
+ 'after',
56
+ '\nPipe the STDOUT to a file such as accept.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer accept.json --from gov1 --keyring-backend="test"',
57
+ )
58
+ .option('--offerId <string>', 'Offer id', String, `operatorAccept-${now()}`)
59
+ .action(async opts => {
60
+ const networkConfig = await fetchEnvNetworkConfig({ env, fetch });
61
+
62
+ const swk = await makeSmartWalletKit({ delay, fetch }, networkConfig);
63
+ const instance = swk.agoricNames.instance.fastUsdc;
64
+ assert(instance, 'fastUsdc instance not in agoricNames');
65
+
66
+ /** @type {OfferSpec} */
67
+ const offer = {
68
+ id: opts.offerId,
69
+ invitationSpec: {
70
+ source: 'purse',
71
+ instance,
72
+ description: INVITATION_MAKERS_DESC,
73
+ },
74
+ proposal: {},
75
+ };
76
+
77
+ /** @type {ExecuteOfferAction} */
78
+ const bridgeAction = {
79
+ method: 'executeOffer',
80
+ offer,
81
+ };
82
+
83
+ outputActionAndHint(bridgeAction, { stderr, stdout });
84
+ });
85
+
86
+ operator
87
+ .command('attest')
88
+ .description('Attest to an observed Fast USDC transfer')
89
+ .addHelpText(
90
+ 'after',
91
+ '\nPipe the STDOUT to a file such as attest.json, then use the Agoric CLI to broadcast it:\n agoric wallet send --offer attest.json --from gov1 --keyring-backend="test"',
92
+ )
93
+ .requiredOption('--previousOfferId <string>', 'Offer id', String)
94
+ .requiredOption('--forwardingChannel <string>', 'Channel id', String)
95
+ .requiredOption('--recipientAddress <string>', 'bech32 address', String)
96
+ .requiredOption('--blockHash <0xhex>', 'hex hash', parseHex)
97
+ .requiredOption('--blockNumber <number>', 'number', parseNat)
98
+ .requiredOption('--blockTimestamp <number>', 'number', parseNat)
99
+ .requiredOption('--chainId <string>', 'chain id', Number)
100
+ .requiredOption('--amount <number>', 'number', parseNat)
101
+ .requiredOption('--forwardingAddress <string>', 'bech32 address', String)
102
+ .requiredOption('--sender <string>', 'Ethereum address initiating', String)
103
+ .requiredOption('--txHash <0xhexo>', 'hex hash', parseHex)
104
+ .option('--offerId <string>', 'Offer id', String, `operatorAttest-${now()}`)
105
+ .action(async opts => {
106
+ const {
107
+ offerId,
108
+ previousOfferId,
109
+ forwardingChannel,
110
+ recipientAddress,
111
+ amount,
112
+ forwardingAddress,
113
+ sender,
114
+ ...flat
115
+ } = opts;
116
+
117
+ const evidence = harden({
118
+ aux: { forwardingChannel, recipientAddress },
119
+ tx: { amount, forwardingAddress, sender },
120
+ ...flat,
121
+ });
122
+ mustMatch(evidence, CctpTxEvidenceShape);
123
+
124
+ /** @type {OfferSpec} */
125
+ const offer = {
126
+ id: offerId,
127
+ invitationSpec: {
128
+ source: 'continuing',
129
+ previousOffer: previousOfferId,
130
+ invitationMakerName: 'SubmitEvidence',
131
+ invitationArgs: [evidence],
132
+ },
133
+ proposal: {},
134
+ };
135
+
136
+ outputActionAndHint(
137
+ { method: 'executeOffer', offer },
138
+ { stderr, stdout },
139
+ );
140
+ });
141
+
142
+ return operator;
143
+ };
@@ -1,44 +1,67 @@
1
+ /* eslint-env node */
1
2
  /* global globalThis */
2
3
 
3
- import { makeVStorage } from '@agoric/client-utils';
4
- import { depositForBurn, makeProvider } from '../util/cctp.js';
4
+ import {
5
+ fetchEnvNetworkConfig,
6
+ makeVStorage,
7
+ pickEndpoint,
8
+ } from '@agoric/client-utils';
9
+ import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
10
+ import { queryFastUSDCLocalChainAccount } from './util/agoric.js';
11
+ import { depositForBurn, makeProvider } from './util/cctp.js';
5
12
  import {
6
13
  makeSigner,
7
14
  queryForwardingAccount,
8
15
  registerFwdAccount,
9
- } from '../util/noble.js';
10
- import { queryFastUSDCLocalChainAccount } from '../util/agoric.js';
16
+ } from './util/noble.js';
17
+ import { queryUSDCBalance } from './util/bank.js';
11
18
 
12
- /** @import { file } from '../util/file' */
19
+ /**
20
+ * @import { File } from './util/file.js'
21
+ * @import {ConfigOpts} from './config.js';
22
+ */
13
23
  /** @import { VStorage } from '@agoric/client-utils' */
14
24
  /** @import { SigningStargateClient } from '@cosmjs/stargate' */
15
25
  /** @import { JsonRpcProvider as ethProvider } from 'ethers' */
16
26
 
17
- const transfer = async (
18
- /** @type {file} */ configFile,
19
- /** @type {string} */ amount,
20
- /** @type {string} */ destination,
27
+ /**
28
+ * @param {File} configFile
29
+ * @param {string} amount
30
+ * @param {string} EUD
31
+ * @param {typeof globalThis.console} [out]
32
+ * @param {typeof globalThis.fetch} [fetch]
33
+ * @param {VStorage} [vstorage]
34
+ * @param {{signer: SigningStargateClient, address: string}} [nobleSigner]
35
+ * @param {ethProvider} [ethProvider]
36
+ * @param {typeof process.env} [env]
37
+ * @param {typeof globalThis.setTimeout} [setTimeout]
38
+ */
39
+ export const transfer = async (
40
+ configFile,
41
+ amount,
42
+ EUD,
21
43
  out = console,
22
44
  fetch = globalThis.fetch,
23
- /** @type {VStorage | undefined} */ vstorage,
24
- /** @type {{signer: SigningStargateClient, address: string} | undefined} */ nobleSigner,
25
- /** @type {ethProvider | undefined} */ ethProvider,
45
+ vstorage,
46
+ nobleSigner,
47
+ ethProvider,
48
+ env = process.env,
49
+ setTimeout = globalThis.setTimeout,
26
50
  ) => {
27
- const execute = async (
28
- /** @type {import('./config').ConfigOpts} */ config,
29
- ) => {
51
+ const execute = async (/** @type {ConfigOpts} */ config) => {
52
+ const netConfig = await fetchEnvNetworkConfig({ env, fetch });
30
53
  vstorage ||= makeVStorage(
31
54
  { fetch },
32
- { chainName: 'agoric', rpcAddrs: [config.agoricRpc] },
55
+ { chainName: 'agoric', rpcAddrs: [pickEndpoint(netConfig)] },
33
56
  );
34
57
  const agoricAddr = await queryFastUSDCLocalChainAccount(vstorage, out);
35
- const appendedAddr = `${agoricAddr}?EUD=${destination}`;
36
- out.log(`forwarding destination ${appendedAddr}`);
58
+ const encodedAddr = encodeAddressHook(agoricAddr, { EUD });
59
+ out.log(`forwarding destination ${encodedAddr}`);
37
60
 
38
61
  const { exists, address } = await queryForwardingAccount(
39
62
  config.nobleApi,
40
63
  config.nobleToAgoricChannel,
41
- appendedAddr,
64
+ encodedAddr,
42
65
  out,
43
66
  fetch,
44
67
  );
@@ -51,18 +74,30 @@ const transfer = async (
51
74
  signer,
52
75
  signerAddress,
53
76
  config.nobleToAgoricChannel,
54
- appendedAddr,
77
+ encodedAddr,
55
78
  out,
56
79
  );
57
80
  out.log(res);
58
81
  } catch (e) {
59
82
  out.error(
60
- `Error registering noble forwarding account for ${appendedAddr} on channel ${config.nobleToAgoricChannel}`,
83
+ `Error registering noble forwarding account for ${encodedAddr} on channel ${config.nobleToAgoricChannel}`,
61
84
  );
62
85
  throw e;
63
86
  }
64
87
  }
65
88
 
89
+ const destChain = config.destinationChains?.find(chain =>
90
+ EUD.startsWith(chain.bech32Prefix),
91
+ );
92
+ if (!destChain) {
93
+ out.error(
94
+ `No destination chain found in config with matching bech32 prefix for ${EUD}, cannot query destination address`,
95
+ );
96
+ throw new Error();
97
+ }
98
+ const { api, USDCDenom } = destChain;
99
+ const startingBalance = await queryUSDCBalance(EUD, api, USDCDenom, fetch);
100
+
66
101
  ethProvider ||= makeProvider(config.ethRpc);
67
102
  await depositForBurn(
68
103
  ethProvider,
@@ -73,6 +108,34 @@ const transfer = async (
73
108
  amount,
74
109
  out,
75
110
  );
111
+
112
+ const refreshDelayMS = 1200;
113
+ const completeP = /** @type {Promise<void>} */ (
114
+ new Promise((res, rej) => {
115
+ const refreshUSDCBalance = async () => {
116
+ out.log('polling usdc balance');
117
+ const currentBalance = await queryUSDCBalance(
118
+ EUD,
119
+ api,
120
+ USDCDenom,
121
+ fetch,
122
+ );
123
+ if (currentBalance !== startingBalance) {
124
+ res();
125
+ } else {
126
+ setTimeout(() => refreshUSDCBalance().catch(rej), refreshDelayMS);
127
+ }
128
+ };
129
+ refreshUSDCBalance().catch(rej);
130
+ })
131
+ ).catch(e => {
132
+ out.error(
133
+ 'Error checking destination address balance, could not detect completion of transfer.',
134
+ );
135
+ out.error(e.message);
136
+ });
137
+
138
+ await completeP;
76
139
  };
77
140
 
78
141
  let config;
@@ -87,5 +150,3 @@ const transfer = async (
87
150
  }
88
151
  await execute(config);
89
152
  };
90
-
91
- export default { transfer };
@@ -0,0 +1,11 @@
1
+ /** @import { VStorage } from '@agoric/client-utils' */
2
+
3
+ export const queryFastUSDCLocalChainAccount = async (
4
+ /** @type {VStorage} */ vstorage,
5
+ out = console,
6
+ ) => {
7
+ const { value } = await vstorage.readLatest('published.fastUsdc');
8
+ const { settlementAccount } = JSON.parse(JSON.parse(value).values[0]);
9
+ out.log(`settlementAccount: ${settlementAccount}`);
10
+ return settlementAccount;
11
+ };
@@ -0,0 +1,12 @@
1
+ export const queryUSDCBalance = async (
2
+ /** @type {string} */ address,
3
+ /** @type {string} */ api,
4
+ /** @type {string} */ denom,
5
+ /** @type {typeof globalThis.fetch} */ fetch,
6
+ ) => {
7
+ const query = `${api}/cosmos/bank/v1beta1/balances/${address}`;
8
+ const json = await fetch(query).then(res => res.json());
9
+ const amount = json.balances?.find(b => b.denom === denom)?.amount ?? '0';
10
+
11
+ return BigInt(amount);
12
+ };
@@ -67,5 +67,5 @@ export const depositForBurn = async (
67
67
 
68
68
  out.log('Transaction confirmed in block', receipt.blockNumber);
69
69
  out.log('Transaction hash:', receipt.hash);
70
- out.log('USDC transfer initiated successfully, our work here is done.');
70
+ out.log('USDC transfer initiated successfully');
71
71
  };
@@ -27,4 +27,4 @@ export const makeFile = (
27
27
  return { read, write, exists, path };
28
28
  };
29
29
 
30
- /** @typedef {ReturnType<typeof makeFile>} file */
30
+ /** @typedef {ReturnType<typeof makeFile>} File */