@ardrive/turbo-sdk 1.33.1 → 1.34.0-alpha.2

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.
Files changed (68) hide show
  1. package/README.md +49 -6
  2. package/bundles/web.bundle.min.js +103598 -103276
  3. package/lib/cjs/cli/cli.js +1 -1
  4. package/lib/cjs/cli/commands/cryptoFund.js +3 -1
  5. package/lib/cjs/common/payment.js +17 -4
  6. package/lib/cjs/common/signer.js +16 -1
  7. package/lib/cjs/common/token/ario.js +27 -19
  8. package/lib/cjs/common/token/arweave.js +4 -1
  9. package/lib/cjs/common/token/baseEth.js +7 -6
  10. package/lib/cjs/common/token/erc20.js +91 -0
  11. package/lib/cjs/common/token/ethereum.js +38 -18
  12. package/lib/cjs/common/token/index.js +11 -0
  13. package/lib/cjs/common/token/polygon.js +7 -6
  14. package/lib/cjs/common/token/solana.js +11 -2
  15. package/lib/cjs/common/token/usdc.js +83 -0
  16. package/lib/cjs/common/upload.js +6 -1
  17. package/lib/cjs/types.js +3 -0
  18. package/lib/cjs/utils/common.js +71 -4
  19. package/lib/cjs/version.js +1 -1
  20. package/lib/esm/cli/cli.js +1 -1
  21. package/lib/esm/cli/commands/cryptoFund.js +3 -1
  22. package/lib/esm/common/payment.js +17 -4
  23. package/lib/esm/common/signer.js +17 -2
  24. package/lib/esm/common/token/ario.js +27 -19
  25. package/lib/esm/common/token/arweave.js +4 -1
  26. package/lib/esm/common/token/baseEth.js +6 -5
  27. package/lib/esm/common/token/erc20.js +87 -0
  28. package/lib/esm/common/token/ethereum.js +37 -18
  29. package/lib/esm/common/token/index.js +11 -0
  30. package/lib/esm/common/token/polygon.js +6 -5
  31. package/lib/esm/common/token/solana.js +11 -2
  32. package/lib/esm/common/token/usdc.js +76 -0
  33. package/lib/esm/common/upload.js +6 -1
  34. package/lib/esm/types.js +3 -0
  35. package/lib/esm/utils/common.js +66 -5
  36. package/lib/esm/version.js +1 -1
  37. package/lib/types/cli/commands/cryptoFund.d.ts.map +1 -1
  38. package/lib/types/cli/types.d.ts +1 -0
  39. package/lib/types/cli/types.d.ts.map +1 -1
  40. package/lib/types/common/payment.d.ts +1 -1
  41. package/lib/types/common/payment.d.ts.map +1 -1
  42. package/lib/types/common/signer.d.ts +2 -2
  43. package/lib/types/common/signer.d.ts.map +1 -1
  44. package/lib/types/common/token/ario.d.ts +1 -1
  45. package/lib/types/common/token/ario.d.ts.map +1 -1
  46. package/lib/types/common/token/arweave.d.ts +1 -1
  47. package/lib/types/common/token/arweave.d.ts.map +1 -1
  48. package/lib/types/common/token/baseEth.d.ts +5 -0
  49. package/lib/types/common/token/baseEth.d.ts.map +1 -1
  50. package/lib/types/common/token/erc20.d.ts +19 -0
  51. package/lib/types/common/token/erc20.d.ts.map +1 -0
  52. package/lib/types/common/token/ethereum.d.ts +4 -2
  53. package/lib/types/common/token/ethereum.d.ts.map +1 -1
  54. package/lib/types/common/token/index.d.ts.map +1 -1
  55. package/lib/types/common/token/polygon.d.ts +5 -0
  56. package/lib/types/common/token/polygon.d.ts.map +1 -1
  57. package/lib/types/common/token/solana.d.ts +2 -1
  58. package/lib/types/common/token/solana.d.ts.map +1 -1
  59. package/lib/types/common/token/usdc.d.ts +36 -0
  60. package/lib/types/common/token/usdc.d.ts.map +1 -0
  61. package/lib/types/common/upload.d.ts.map +1 -1
  62. package/lib/types/types.d.ts +8 -2
  63. package/lib/types/types.d.ts.map +1 -1
  64. package/lib/types/utils/common.d.ts +6 -0
  65. package/lib/types/utils/common.d.ts.map +1 -1
  66. package/lib/types/version.d.ts +1 -1
  67. package/lib/types/version.d.ts.map +1 -1
  68. package/package.json +1 -1
@@ -7,6 +7,12 @@ exports.createTurboSigner = createTurboSigner;
7
7
  exports.signerFromKyvePrivateKey = signerFromKyvePrivateKey;
8
8
  exports.signerFromKyveMnemonic = signerFromKyveMnemonic;
9
9
  exports.isBlob = isBlob;
10
+ exports.isValidArweaveBase64URL = isValidArweaveBase64URL;
11
+ exports.isValidSolanaAddress = isValidSolanaAddress;
12
+ exports.isValidECDSAAddress = isValidECDSAAddress;
13
+ exports.isValidKyveAddress = isValidKyveAddress;
14
+ exports.isValidUserAddress = isValidUserAddress;
15
+ exports.isAnyValidUserAddress = isAnyValidUserAddress;
10
16
  /**
11
17
  * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
12
18
  *
@@ -28,6 +34,7 @@ const encoding_1 = require("@cosmjs/encoding");
28
34
  const arbundles_1 = require("@dha-team/arbundles");
29
35
  const bytes_1 = require("@ethersproject/bytes");
30
36
  const signing_key_1 = require("@ethersproject/signing-key");
37
+ const web3_js_1 = require("@solana/web3.js");
31
38
  const ethers_1 = require("ethers");
32
39
  const types_js_1 = require("../types.js");
33
40
  function sleep(ms) {
@@ -36,15 +43,21 @@ function sleep(ms) {
36
43
  function isWeb() {
37
44
  return typeof window !== 'undefined';
38
45
  }
46
+ const ethTestnetRpc = 'https://eth-sepolia.public.blastapi.io';
47
+ const baseTestnetRpc = 'https://sepolia.base.org';
48
+ const polygonTestnetRpc = 'https://rpc-amoy.polygon.technology';
39
49
  exports.tokenToDevGatewayMap = {
40
50
  arweave: 'https://arweave.net', // No arweave test net
41
51
  ario: 'https://arweave.net', // No arweave test net
42
52
  solana: 'https://api.devnet.solana.com',
43
- ethereum: 'https://ethereum-holesky-rpc.publicnode.com',
44
- 'base-eth': 'https://sepolia.base.org',
53
+ ethereum: ethTestnetRpc,
54
+ 'base-eth': baseTestnetRpc,
45
55
  kyve: 'https://api.korellia.kyve.network',
46
- matic: 'https://rpc-amoy.polygon.technology',
47
- pol: 'https://rpc-amoy.polygon.technology',
56
+ matic: polygonTestnetRpc,
57
+ pol: polygonTestnetRpc,
58
+ usdc: ethTestnetRpc,
59
+ 'base-usdc': baseTestnetRpc,
60
+ 'polygon-usdc': polygonTestnetRpc,
48
61
  };
49
62
  exports.tokenToDevAoConfigMap = {
50
63
  ario: {
@@ -61,6 +74,9 @@ exports.defaultProdGatewayUrls = {
61
74
  kyve: 'https://api.kyve.network/',
62
75
  matic: 'https://polygon-rpc.com/',
63
76
  pol: 'https://polygon-rpc.com/',
77
+ usdc: 'https://cloudflare-eth.com/',
78
+ 'base-usdc': 'https://mainnet.base.org',
79
+ 'polygon-usdc': 'https://polygon-rpc.com/',
64
80
  };
65
81
  exports.defaultProdAoConfigs = {
66
82
  ario: {
@@ -92,6 +108,9 @@ function createTurboSigner({ signer: clientProvidedSigner, privateKey: clientPro
92
108
  case 'pol':
93
109
  case 'matic':
94
110
  case 'base-eth':
111
+ case 'usdc':
112
+ case 'base-usdc':
113
+ case 'polygon-usdc':
95
114
  if (!(0, types_js_1.isEthPrivateKey)(clientProvidedPrivateKey)) {
96
115
  throw new Error('A valid Ethereum private key must be provided for EthereumSigner.');
97
116
  }
@@ -123,3 +142,51 @@ async function signerFromKyveMnemonic(mnemonic) {
123
142
  function isBlob(val) {
124
143
  return typeof Blob !== 'undefined' && val instanceof Blob;
125
144
  }
145
+ // check if it is a valid arweave base64url for a wallet public address, transaction id or smartweave contract
146
+ function isValidArweaveBase64URL(base64URL) {
147
+ const base64URLRegex = new RegExp('^[a-zA-Z0-9_-]{43}$');
148
+ return base64URLRegex.test(base64URL);
149
+ }
150
+ function isValidSolanaAddress(address) {
151
+ try {
152
+ return web3_js_1.PublicKey.isOnCurve(address);
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ function isValidECDSAAddress(address) {
159
+ const ethAddressRegex = new RegExp('^0x[a-fA-F0-9]{40}$');
160
+ return ethAddressRegex.test(address);
161
+ }
162
+ function isValidKyveAddress(address) {
163
+ const kyveAddressRegex = new RegExp('^kyve[a-zA-Z0-9]{39}$');
164
+ return kyveAddressRegex.test(address);
165
+ }
166
+ function isValidUserAddress(address, type) {
167
+ switch (type) {
168
+ case 'arweave':
169
+ case 'ario':
170
+ return isValidArweaveBase64URL(address);
171
+ case 'solana':
172
+ return isValidSolanaAddress(address);
173
+ case 'ethereum':
174
+ case 'base-eth':
175
+ case 'matic':
176
+ case 'pol':
177
+ case 'base-usdc':
178
+ case 'usdc':
179
+ case 'polygon-usdc':
180
+ return isValidECDSAAddress(address);
181
+ case 'kyve':
182
+ return isValidKyveAddress(address);
183
+ }
184
+ }
185
+ function isAnyValidUserAddress(address) {
186
+ for (const type of types_js_1.tokenTypes) {
187
+ if (isValidUserAddress(address, type)) {
188
+ return type;
189
+ }
190
+ }
191
+ return false;
192
+ }
@@ -17,4 +17,4 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.version = void 0;
19
19
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
20
- exports.version = '1.33.1';
20
+ exports.version = '1.34.0-alpha.2';
@@ -39,7 +39,7 @@ applyOptions(program.command('balance').description('Get balance of a Turbo addr
39
39
  applyOptions(program.command('top-up').description('Top up a Turbo address with Fiat'), [...walletOptions, optionMap.address, optionMap.value, optionMap.currency]).action(async (_commandOptions, command) => {
40
40
  await runCommand(command, topUp);
41
41
  });
42
- applyOptions(program.command('crypto-fund').description('Top up a wallet with crypto'), [...walletOptions, optionMap.value, optionMap.txId]).action(async (_commandOptions, command) => {
42
+ applyOptions(program.command('crypto-fund').description('Top up a wallet with crypto'), [...walletOptions, optionMap.value, optionMap.txId, optionMap.address]).action(async (_commandOptions, command) => {
43
43
  await runCommand(command, cryptoFund);
44
44
  });
45
45
  applyOptions(program.command('upload-folder').description('Upload a folder using Turbo'), uploadFolderOptions).action(async (_commandOptions, command) => {
@@ -22,6 +22,7 @@ import { configFromOptions, tokenFromOptions, turboFromOptions, } from '../utils
22
22
  export async function cryptoFund(options) {
23
23
  const value = options.value;
24
24
  const txId = options.txId;
25
+ const address = options.address;
25
26
  if (txId !== undefined) {
26
27
  const turbo = TurboFactory.unauthenticated(configFromOptions(options));
27
28
  const result = await turbo.submitFundTransaction({ txId: txId });
@@ -41,7 +42,7 @@ export async function cryptoFund(options) {
41
42
  const { confirm } = await prompts({
42
43
  type: 'confirm',
43
44
  name: 'confirm',
44
- message: `\nTransaction details:\n\n Amount: ${value} ${token}\n Target: ${targetWallet}\n Est Credits to receive: ${credits}\n Credit recipient: ${await turbo.signer.getNativeAddress()}\n Note: Network Dependent Gas Fees May Apply\n\nThis payment is non-refundable. Proceed with transaction?`,
45
+ message: `\nTransaction details:\n\n Amount: ${value} ${token}\n Target: ${targetWallet}\n Est Credits to receive: ${credits}\n Credit recipient: ${address ?? (await turbo.signer.getNativeAddress())}\n Note: Network Dependent Gas Fees May Apply\n\nThis payment is non-refundable. Proceed with transaction?`,
45
46
  initial: true,
46
47
  });
47
48
  if (!confirm) {
@@ -51,6 +52,7 @@ export async function cryptoFund(options) {
51
52
  }
52
53
  const result = await turbo.topUpWithTokens({
53
54
  tokenAmount,
55
+ turboCreditDestinationAddress: address,
54
56
  });
55
57
  console.log('Sent crypto fund transaction: \n', JSON.stringify(result, null, 2));
56
58
  }
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { BigNumber } from 'bignumber.js';
17
17
  import { defaultRetryConfig } from '../utils/axiosClient.js';
18
+ import { isAnyValidUserAddress } from '../utils/common.js';
18
19
  import { TurboHTTPService } from './http.js';
19
20
  import { TurboWinstonLogger } from './logger.js';
20
21
  import { exponentMap, tokenToBaseMap } from './token/index.js';
@@ -148,31 +149,37 @@ export class TurboUnauthenticatedPaymentService {
148
149
  return {
149
150
  id: response.creditedTransaction.transactionId,
150
151
  quantity: response.creditedTransaction.transactionQuantity,
151
- owner: response.creditedTransaction.destinationAddress,
152
+ owner: response.creditedTransaction.transactionSenderAddress ??
153
+ response.creditedTransaction.destinationAddress,
152
154
  winc: response.creditedTransaction.winstonCreditAmount,
153
155
  token: response.creditedTransaction.tokenType,
154
156
  status: 'confirmed',
155
157
  block: response.creditedTransaction.blockHeight,
158
+ recipient: response.creditedTransaction.destinationAddress,
156
159
  };
157
160
  }
158
161
  else if ('pendingTransaction' in response) {
159
162
  return {
160
163
  id: response.pendingTransaction.transactionId,
161
164
  quantity: response.pendingTransaction.transactionQuantity,
162
- owner: response.pendingTransaction.destinationAddress,
165
+ owner: response.pendingTransaction.transactionSenderAddress ??
166
+ response.pendingTransaction.destinationAddress,
163
167
  winc: response.pendingTransaction.winstonCreditAmount,
164
168
  token: response.pendingTransaction.tokenType,
165
169
  status: 'pending',
170
+ recipient: response.pendingTransaction.destinationAddress,
166
171
  };
167
172
  }
168
173
  else if ('failedTransaction' in response) {
169
174
  return {
170
175
  id: response.failedTransaction.transactionId,
171
176
  quantity: response.failedTransaction.transactionQuantity,
172
- owner: response.failedTransaction.destinationAddress,
177
+ owner: response.failedTransaction.transactionSenderAddress ??
178
+ response.failedTransaction.destinationAddress,
173
179
  winc: response.failedTransaction.winstonCreditAmount,
174
180
  token: response.failedTransaction.tokenType,
175
181
  status: 'failed',
182
+ recipient: response.failedTransaction.destinationAddress,
176
183
  };
177
184
  }
178
185
  throw new Error('Unknown response from payment service: ' + response);
@@ -267,10 +274,15 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
267
274
  }
268
275
  return walletAddress;
269
276
  }
270
- async topUpWithTokens({ feeMultiplier = 1, tokenAmount: tokenAmountV, }) {
277
+ async topUpWithTokens({ feeMultiplier = 1, tokenAmount: tokenAmountV, turboCreditDestinationAddress, }) {
271
278
  if (!this.tokenTools) {
272
279
  throw new Error(`Token type not supported for crypto fund ${this.token}`);
273
280
  }
281
+ if (turboCreditDestinationAddress !== undefined) {
282
+ if (isAnyValidUserAddress(turboCreditDestinationAddress) === false) {
283
+ throw new Error(`Invalid turboCreditDestinationAddress provided: ${turboCreditDestinationAddress}`);
284
+ }
285
+ }
274
286
  const tokenAmount = new BigNumber(tokenAmountV);
275
287
  const target = await this.getTargetWalletForFund();
276
288
  this.logger.debug('Funding account...', {
@@ -283,6 +295,7 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
283
295
  tokenAmount,
284
296
  feeMultiplier,
285
297
  signer: this.signer,
298
+ turboCreditDestinationAddress,
286
299
  });
287
300
  const txId = fundTx.id;
288
301
  try {
@@ -18,7 +18,7 @@ import { Secp256k1 } from '@cosmjs/crypto';
18
18
  import { toBase64 } from '@cosmjs/encoding';
19
19
  import { EthereumSigner, HexSolanaSigner } from '@dha-team/arbundles';
20
20
  import { computePublicKey } from '@ethersproject/signing-key';
21
- import { Connection, PublicKey, SystemProgram, Transaction, } from '@solana/web3.js';
21
+ import { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, } from '@solana/web3.js';
22
22
  import { BigNumber } from 'bignumber.js';
23
23
  import bs58 from 'bs58';
24
24
  import { randomBytes } from 'crypto';
@@ -28,6 +28,8 @@ import nacl from 'tweetnacl';
28
28
  import { isEthereumWalletAdapter, isSolanaWalletAdapter, } from '../types.js';
29
29
  import { fromB64Url, ownerToAddress as ownerToB64Address, toB64Url, } from '../utils/base64.js';
30
30
  import { TurboWinstonLogger } from './logger.js';
31
+ import { ethDataFromTurboCreditDestinationAddress } from './token/ethereum.js';
32
+ import { memoProgramId } from './token/solana.js';
31
33
  /**
32
34
  * Abstract class for signing TurboDataItems.
33
35
  */
@@ -46,6 +48,9 @@ export class TurboDataItemAbstractSigner {
46
48
  case 'matic':
47
49
  case 'pol':
48
50
  case 'base-eth':
51
+ case 'usdc':
52
+ case 'base-usdc':
53
+ case 'polygon-usdc':
49
54
  return computeAddress(computePublicKey(fromB64Url(owner)));
50
55
  case 'kyve':
51
56
  return pubkeyToAddress({
@@ -75,7 +80,7 @@ export class TurboDataItemAbstractSigner {
75
80
  return this.ownerToNativeAddress(toB64Url(await this.getPublicKey()), this.token);
76
81
  }
77
82
  /** Let the signer handle sending tx for better compat with cross chain libraries/web wallets */
78
- async sendTransaction({ target, amount, gatewayUrl, }) {
83
+ async sendTransaction({ target, amount, gatewayUrl, turboCreditDestinationAddress, }) {
79
84
  if (this.walletAdapter) {
80
85
  if (isSolanaWalletAdapter(this.walletAdapter)) {
81
86
  const connection = new Connection(gatewayUrl, 'confirmed');
@@ -89,6 +94,14 @@ export class TurboDataItemAbstractSigner {
89
94
  toPubkey: new PublicKey(target),
90
95
  lamports: +new BigNumber(amount),
91
96
  }));
97
+ if (turboCreditDestinationAddress !== undefined) {
98
+ tx.add(new TransactionInstruction({
99
+ programId: new PublicKey(memoProgramId),
100
+ keys: [],
101
+ data: Buffer.from('turboCreditDestinationAddress=' +
102
+ turboCreditDestinationAddress),
103
+ }));
104
+ }
92
105
  const signedTx = await this.walletAdapter.signTransaction(tx);
93
106
  const id = await connection.sendRawTransaction(signedTx.serialize());
94
107
  return id;
@@ -103,6 +116,7 @@ export class TurboDataItemAbstractSigner {
103
116
  const { hash } = await signer.sendTransaction({
104
117
  to: target,
105
118
  value: parseEther(amount.toFixed(18)),
119
+ data: ethDataFromTurboCreditDestinationAddress(turboCreditDestinationAddress),
106
120
  });
107
121
  return hash;
108
122
  }
@@ -115,6 +129,7 @@ export class TurboDataItemAbstractSigner {
115
129
  const tx = await ethWalletAndProvider.sendTransaction({
116
130
  to: target,
117
131
  value: parseEther(amount.toFixed(18)),
132
+ data: ethDataFromTurboCreditDestinationAddress(turboCreditDestinationAddress),
118
133
  });
119
134
  this.logger.debug('Sent transaction', { tx });
120
135
  return tx.hash;
@@ -32,34 +32,42 @@ export class ARIOToken {
32
32
  this.pollingOptions = pollingOptions;
33
33
  this.logger = logger;
34
34
  }
35
- async createAndSubmitTx({ target, signer: { signer }, tokenAmount, }) {
35
+ async createAndSubmitTx({ target, signer: { signer }, tokenAmount, turboCreditDestinationAddress, }) {
36
+ const tags = [
37
+ {
38
+ name: 'Action',
39
+ value: 'Transfer',
40
+ },
41
+ {
42
+ name: 'Recipient',
43
+ value: target,
44
+ },
45
+ {
46
+ name: 'Quantity',
47
+ value: tokenAmount.toString(),
48
+ },
49
+ {
50
+ name: 'Turbo-SDK',
51
+ value: version,
52
+ },
53
+ ];
54
+ if (turboCreditDestinationAddress !== undefined) {
55
+ tags.push({
56
+ name: 'Turbo-Credit-Destination-Address',
57
+ value: turboCreditDestinationAddress,
58
+ });
59
+ }
36
60
  const txId = await this.ao.message({
37
61
  signer: createAoSigner(signer),
38
62
  process: this.processId,
39
- tags: [
40
- {
41
- name: 'Action',
42
- value: 'Transfer',
43
- },
44
- {
45
- name: 'Recipient',
46
- value: target,
47
- },
48
- {
49
- name: 'Quantity',
50
- value: tokenAmount.toString(),
51
- },
52
- {
53
- name: 'Turbo-SDK',
54
- value: version,
55
- },
56
- ],
63
+ tags,
57
64
  });
58
65
  this.logger.debug('Submitted Transfer message to ARIO process...', {
59
66
  id: txId,
60
67
  target,
61
68
  tokenAmount,
62
69
  processId: this.processId,
70
+ tags,
63
71
  });
64
72
  return { id: txId, target, reward: '0' };
65
73
  }
@@ -40,7 +40,7 @@ export class ArweaveToken {
40
40
  this.mintU = mintU;
41
41
  this.pollingOptions = pollingOptions;
42
42
  }
43
- async createAndSubmitTx({ feeMultiplier, target, tokenAmount, signer, }) {
43
+ async createAndSubmitTx({ feeMultiplier, target, tokenAmount, signer, turboCreditDestinationAddress, }) {
44
44
  const tx = await this.arweave.createTransaction({
45
45
  target,
46
46
  quantity: tokenAmount.toString(),
@@ -57,6 +57,9 @@ export class ArweaveToken {
57
57
  tx.addTag('Contract', 'KTzTXT_ANmF84fWEKHzWURD1LWd9QaFR9yfYUwH2Lxw'); // cspell:enable
58
58
  tx.addTag('Input', JSON.stringify({ function: 'mint' }));
59
59
  }
60
+ if (turboCreditDestinationAddress !== undefined) {
61
+ tx.addTag('Turbo-Credit-Destination-Address', turboCreditDestinationAddress);
62
+ }
60
63
  const publicKeyB64Url = toB64Url(await signer.getPublicKey());
61
64
  tx.setOwner(publicKeyB64Url);
62
65
  const dataToSign = await tx.getSignatureData();
@@ -1,11 +1,12 @@
1
1
  import { defaultProdGatewayUrls } from '../../utils/common.js';
2
2
  import { EthereumToken } from './ethereum.js';
3
+ export const defaultBaseNetworkPollingOptions = {
4
+ initialBackoffMs: 2_500,
5
+ maxAttempts: 10,
6
+ pollingIntervalMs: 750,
7
+ };
3
8
  export class BaseEthToken extends EthereumToken {
4
- constructor({ logger, gatewayUrl = defaultProdGatewayUrls['base-eth'], pollingOptions = {
5
- initialBackoffMs: 2_500,
6
- maxAttempts: 10,
7
- pollingIntervalMs: 2_500,
8
- }, } = {}) {
9
+ constructor({ logger, gatewayUrl = defaultProdGatewayUrls['base-eth'], pollingOptions = defaultBaseNetworkPollingOptions, } = {}) {
9
10
  super({
10
11
  logger,
11
12
  gatewayUrl,
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { EthereumSigner } from '@dha-team/arbundles';
17
+ import { Wallet as EthereumWallet, JsonRpcProvider, ethers } from 'ethers';
18
+ import { isEthereumWalletAdapter, } from '../../types.js';
19
+ import { defaultProdGatewayUrls } from '../../utils/common.js';
20
+ import { TurboWinstonLogger } from '../logger.js';
21
+ import { EthereumToken, ethDataFromTurboCreditDestinationAddress, } from './ethereum.js';
22
+ export class ERC20Token extends EthereumToken {
23
+ constructor({ tokenContractAddress, logger = TurboWinstonLogger.default, gatewayUrl = defaultProdGatewayUrls.ethereum, pollingOptions, }) {
24
+ super({ logger, gatewayUrl, pollingOptions });
25
+ this.tokenContract = new ethers.Contract(tokenContractAddress, [
26
+ 'function decimals() view returns (uint8)',
27
+ 'function balanceOf(address) view returns (uint256)',
28
+ 'function transfer(address to, uint256 value) returns (bool)',
29
+ ], this.rpcProvider);
30
+ }
31
+ async createAndSubmitTx({ target, tokenAmount, signer, turboCreditDestinationAddress, }) {
32
+ try {
33
+ let connected;
34
+ let walletOrSigner;
35
+ if (signer.signer instanceof EthereumSigner) {
36
+ const provider = new JsonRpcProvider(this.gatewayUrl);
37
+ // 🧩 CLI / Node path
38
+ const keyHex = Buffer.from(signer.signer.key).toString('hex');
39
+ walletOrSigner = new EthereumWallet(keyHex, provider);
40
+ connected = this.tokenContract.connect(walletOrSigner);
41
+ }
42
+ else if (signer.walletAdapter !== undefined &&
43
+ isEthereumWalletAdapter(signer.walletAdapter)) {
44
+ walletOrSigner = signer.walletAdapter.getSigner();
45
+ connected = this.tokenContract.connect(walletOrSigner);
46
+ }
47
+ else {
48
+ throw new Error('Unsupported signer -- must be EthereumSigner or have a walletAdapter implementing getSigner');
49
+ }
50
+ // Encode transfer data
51
+ const baseTransferData = connected.interface.encodeFunctionData('transfer', [target, tokenAmount.toString()]);
52
+ let finalData = baseTransferData;
53
+ // Append optional memo data with turbo credit destination address
54
+ const memoData = ethDataFromTurboCreditDestinationAddress(turboCreditDestinationAddress);
55
+ if (memoData !== undefined) {
56
+ // remove the "0x" prefix and append
57
+ finalData += memoData.slice(2);
58
+ }
59
+ const txRequest = {
60
+ to: await connected.getAddress(),
61
+ data: finalData,
62
+ };
63
+ this.logger.debug('Submitting ERC20 transfer', {
64
+ target,
65
+ tokenAmount: tokenAmount.toString(),
66
+ rpcEndpoint: this.gatewayUrl,
67
+ txRequest,
68
+ });
69
+ const tx = await walletOrSigner.sendTransaction(txRequest);
70
+ this.logger.debug('ERC20 transfer submitted', {
71
+ txHash: tx.hash,
72
+ target,
73
+ tx,
74
+ });
75
+ return { id: tx.hash, target };
76
+ }
77
+ catch (e) {
78
+ this.logger.error('Error creating/submitting ERC20 tx', {
79
+ error: e instanceof Error ? e.message : e,
80
+ target,
81
+ tokenAmount,
82
+ rpcEndpoint: this.gatewayUrl,
83
+ });
84
+ throw e;
85
+ }
86
+ }
87
+ }
@@ -14,34 +14,47 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { BigNumber } from 'bignumber.js';
17
- import { ethers } from 'ethers';
17
+ import { ethers, hexlify, toUtf8Bytes } from 'ethers';
18
18
  import { defaultProdGatewayUrls } from '../../utils/common.js';
19
19
  import { TurboWinstonLogger } from '../logger.js';
20
20
  export const weiToTokenAmount = (wei) => wei;
21
21
  export const ETHToTokenAmount = (eth) => new BigNumber(eth).times(1e18).valueOf();
22
+ export const defaultEthereumPollingOptions = {
23
+ initialBackoffMs: 25_000,
24
+ maxAttempts: 10,
25
+ pollingIntervalMs: 1_500,
26
+ };
22
27
  export class EthereumToken {
23
- constructor({ logger = TurboWinstonLogger.default, gatewayUrl = defaultProdGatewayUrls.ethereum, pollingOptions = {
24
- maxAttempts: 10,
25
- pollingIntervalMs: 4_000,
26
- initialBackoffMs: 10_000,
27
- }, } = {}) {
28
+ constructor({ logger = TurboWinstonLogger.default, gatewayUrl = defaultProdGatewayUrls.ethereum, pollingOptions = defaultEthereumPollingOptions, } = {}) {
28
29
  this.logger = logger;
29
30
  this.gatewayUrl = gatewayUrl;
30
31
  this.pollingOptions = pollingOptions;
31
32
  this.rpcProvider = new ethers.JsonRpcProvider(gatewayUrl);
32
33
  }
33
- async createAndSubmitTx({ target, tokenAmount, signer, }) {
34
- // convert wei to eth
35
- const eth = tokenAmount.shiftedBy(-18);
36
- const txId = await signer.sendTransaction({
37
- target,
38
- amount: eth,
39
- gatewayUrl: this.gatewayUrl,
40
- });
41
- return {
42
- id: txId,
43
- target,
44
- };
34
+ async createAndSubmitTx({ target, tokenAmount, signer, turboCreditDestinationAddress, }) {
35
+ try {
36
+ // convert wei to eth
37
+ const eth = tokenAmount.shiftedBy(-18);
38
+ const txId = await signer.sendTransaction({
39
+ target,
40
+ amount: eth,
41
+ gatewayUrl: this.gatewayUrl,
42
+ turboCreditDestinationAddress,
43
+ });
44
+ return {
45
+ id: txId,
46
+ target,
47
+ };
48
+ }
49
+ catch (e) {
50
+ this.logger.error('Error creating and submitting Ethereum tx', {
51
+ error: e instanceof Error ? e.message : e,
52
+ target,
53
+ tokenAmount,
54
+ rpcEndpoint: this.gatewayUrl,
55
+ });
56
+ throw e;
57
+ }
45
58
  }
46
59
  async getTxAvailability(txId) {
47
60
  const tx = await this.rpcProvider.getTransaction(txId);
@@ -71,3 +84,9 @@ export class EthereumToken {
71
84
  throw new Error(`Transaction ${txId} not found after polling!`);
72
85
  }
73
86
  }
87
+ export function ethDataFromTurboCreditDestinationAddress(turboCreditDestinationAddress) {
88
+ if (turboCreditDestinationAddress !== undefined) {
89
+ return hexlify(toUtf8Bytes('turboCreditDestinationAddress=' + turboCreditDestinationAddress));
90
+ }
91
+ return undefined;
92
+ }
@@ -6,6 +6,7 @@ import { ETHToTokenAmount, EthereumToken } from './ethereum.js';
6
6
  import { KYVEToTokenAmount, KyveToken } from './kyve.js';
7
7
  import { POLToTokenAmount, PolygonToken } from './polygon.js';
8
8
  import { SOLToTokenAmount, SolanaToken } from './solana.js';
9
+ import { USDCToTokenAmount, USDCToken } from './usdc.js';
9
10
  export const defaultTokenMap = {
10
11
  arweave: (config) => new ArweaveToken(config),
11
12
  ario: (config) => new ARIOToken(config),
@@ -15,8 +16,12 @@ export const defaultTokenMap = {
15
16
  kyve: (config) => new KyveToken(config),
16
17
  matic: (config) => new PolygonToken(config),
17
18
  pol: (config) => new PolygonToken(config),
19
+ usdc: (config) => new USDCToken({ network: 'ethereum', ...config }),
20
+ 'base-usdc': (config) => new USDCToken({ network: 'base', ...config }),
21
+ 'polygon-usdc': (config) => new USDCToken({ network: 'polygon', ...config }),
18
22
  };
19
23
  const ethExponent = 18;
24
+ const usdcExponent = 6;
20
25
  export const exponentMap = {
21
26
  arweave: 12,
22
27
  ario: 6,
@@ -26,6 +31,9 @@ export const exponentMap = {
26
31
  kyve: 6,
27
32
  matic: ethExponent,
28
33
  pol: ethExponent,
34
+ usdc: usdcExponent,
35
+ 'base-usdc': usdcExponent,
36
+ 'polygon-usdc': usdcExponent,
29
37
  };
30
38
  export const tokenToBaseMap = {
31
39
  arweave: (a) => ARToTokenAmount(a),
@@ -36,6 +44,9 @@ export const tokenToBaseMap = {
36
44
  kyve: (a) => KYVEToTokenAmount(a),
37
45
  matic: (a) => POLToTokenAmount(a),
38
46
  pol: (a) => POLToTokenAmount(a),
47
+ usdc: (a) => USDCToTokenAmount(a),
48
+ 'base-usdc': (a) => USDCToTokenAmount(a),
49
+ 'polygon-usdc': (a) => USDCToTokenAmount(a),
39
50
  };
40
51
  export function isTokenType(token) {
41
52
  return tokenTypes.includes(token);
@@ -2,12 +2,13 @@ import { defaultProdGatewayUrls } from '../../utils/common.js';
2
2
  import { TurboWinstonLogger } from '../logger.js';
3
3
  import { ETHToTokenAmount, EthereumToken } from './ethereum.js';
4
4
  export const POLToTokenAmount = ETHToTokenAmount;
5
+ export const defaultPolygonPollingOptions = {
6
+ maxAttempts: 10,
7
+ initialBackoffMs: 5_000,
8
+ pollingIntervalMs: 1_000,
9
+ };
5
10
  export class PolygonToken extends EthereumToken {
6
- constructor({ logger = TurboWinstonLogger.default, gatewayUrl = defaultProdGatewayUrls.pol, pollingOptions = {
7
- maxAttempts: 10,
8
- pollingIntervalMs: 4_000,
9
- initialBackoffMs: 5_000,
10
- }, } = {}) {
11
+ constructor({ logger = TurboWinstonLogger.default, gatewayUrl = defaultProdGatewayUrls.pol, pollingOptions = defaultPolygonPollingOptions, } = {}) {
11
12
  super({ logger, gatewayUrl, pollingOptions });
12
13
  }
13
14
  }