diff --git a/CHANGELOG.md b/CHANGELOG.md index 1277cc74..6c2ded54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ ### Breaking - `Asset#toAtomicAmount` now returns a BigInt instead of a Decimal +## [0.5.1] - 2024-09-12 + +### Fixed +- Fixed a bug that blocked arbitrum mainnet wallets from being created + ## [0.5.0] - 2024-09-11 - Add Arbitrum-Mainnet support for Native transfers. diff --git a/jest.config.js b/jest.config.js index 1ce27854..17790c6a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,10 +10,10 @@ module.exports = { maxWorkers: 1, coverageThreshold: { "./src/coinbase/**": { - branches: 78, + branches: 77, functions: 90, statements: 85, - lines: 88, + lines: 87, }, }, }; diff --git a/quickstart-template/package-lock.json b/quickstart-template/package-lock.json index fc398e86..18ff1675 100644 --- a/quickstart-template/package-lock.json +++ b/quickstart-template/package-lock.json @@ -1,17 +1,18 @@ { "name": "quickstart-template", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quickstart-template", - "version": "1.0.0", + "version": "1.1.0", "license": "ISC", "dependencies": { - "@coinbase/coinbase-sdk": "^0.0.13", + "@coinbase/coinbase-sdk": "^0.5.1", "csv-parse": "^5.5.6", - "csv-writer": "^1.6.0" + "csv-writer": "^1.6.0", + "viem": "^2.21.6" } }, "node_modules/@adraffy/ens-normalize": { @@ -20,13 +21,14 @@ "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, "node_modules/@coinbase/coinbase-sdk": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@coinbase/coinbase-sdk/-/coinbase-sdk-0.0.13.tgz", - "integrity": "sha512-Hl1+7XD2elxnb1PJ/htZr5NV2Gqe6rcHsAplG4lg8QzAnXV0YioA3RwbK7UJszQ9r90suPI2ts9AFadqYO+1CA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@coinbase/coinbase-sdk/-/coinbase-sdk-0.5.1.tgz", + "integrity": "sha512-z2CdMZV9UvyZ33DZT7HOpz2YxGhjaYVwGEb8izmJfUXm7Rcm6eg3MLL/dizNiOEbDMaO1Mj7LwKqf+tQnDU+Qw==", "dependencies": { "@scure/bip32": "^1.4.0", "axios": "^1.6.8", "axios-mock-adapter": "^1.22.0", + "axios-retry": "^4.4.1", "bip32": "^4.0.0", "bip39": "^3.1.0", "decimal.js": "^10.4.3", @@ -59,9 +61,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", - "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", + "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -79,11 +81,54 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/bip39": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", + "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", + "dependencies": { + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@types/node": { "version": "18.15.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" }, + "node_modules/abitype": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", + "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", @@ -116,6 +161,17 @@ "axios": ">= 0.17.0" } }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, "node_modules/base-x": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", @@ -473,6 +529,31 @@ "node": ">=4" } }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isows": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", + "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "peerDependencies": { + "ws": "*" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -681,6 +762,67 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/viem": { + "version": "2.21.6", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.6.tgz", + "integrity": "sha512-YX48IVl6nZ4FRsY4ypv2RrxtQVWysIY146/lBW53tma8u32h8EsiA7vecw9ZbrueNUy/asHR4Egu68Z6FOvDzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.4.0", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.4.0", + "abitype": "1.0.5", + "isows": "1.0.4", + "webauthn-p256": "0.0.5", + "ws": "8.17.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/webauthn-p256": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.5.tgz", + "integrity": "sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, "node_modules/wif": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", diff --git a/quickstart-template/package.json b/quickstart-template/package.json index c793fbed..8d39fdef 100644 --- a/quickstart-template/package.json +++ b/quickstart-template/package.json @@ -7,7 +7,8 @@ "start": "node index.js", "start-trade-assets": "node trade-assets.js", "start-mass-payout": "node mass-payout.js", - "start-webhook": "node webhook.js" + "start-webhook": "node webhook.js", + "start-register-basename": "node register-basename.js" }, "keywords": [], "author": "", @@ -16,6 +17,7 @@ "dependencies": { "@coinbase/coinbase-sdk": "^0.6.0", "csv-parse": "^5.5.6", - "csv-writer": "^1.6.0" + "csv-writer": "^1.6.0", + "viem": "^2.21.6" } } diff --git a/quickstart-template/register-basename.js b/quickstart-template/register-basename.js new file mode 100644 index 00000000..e95b6c49 --- /dev/null +++ b/quickstart-template/register-basename.js @@ -0,0 +1,168 @@ +import { Coinbase, Wallet } from "@coinbase/coinbase-sdk"; +import { encodeFunctionData, namehash } from "viem"; +import os from "os"; + +// Relevant ABI for L2 Resolver Contract. +const l2ResolverABI = [ + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "a", type: "address" }, + ], + name: "setAddr", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "string", name: "newName", type: "string" }, + ], + name: "setName", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; + +// Relevant ABI for Basenames Registrar Controller Contract. +const registrarABI = [ + { + inputs: [ + { + components: [ + { + internalType: "string", + name: "name", + type: "string", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "duration", + type: "uint256", + }, + { + internalType: "address", + name: "resolver", + type: "address", + }, + { + internalType: "bytes[]", + name: "data", + type: "bytes[]", + }, + { + internalType: "bool", + name: "reverseRecord", + type: "bool", + }, + ], + internalType: "struct RegistrarController.RegisterRequest", + name: "request", + type: "tuple", + }, + ], + name: "register", + outputs: [], + stateMutability: "payable", + type: "function", + }, +]; + +// Basenames Registrar Controller Contract Address. +const BaseNamesRegistrarControllerAddress = "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; + +// L2 Resolver Contract Address. +const L2ResolverAddress = "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"; + +// Create register contract method arguments. +function createRegisterContractMethodArgs(baseName, addressId) { + const addressData = encodeFunctionData({ + abi: l2ResolverABI, + functionName: "setAddr", + args: [namehash(baseName), addressId], + }); + const nameData = encodeFunctionData({ + abi: l2ResolverABI, + functionName: "setName", + args: [namehash(baseName), baseName], + }); + + const registerArgs = { + request: [ + baseName.replace(/\.base\.eth$/, ""), + addressId, + "31557600", + L2ResolverAddress, + [addressData, nameData], + true, + ], + }; + console.log(`Register contract method arguments constructed: `, registerArgs); + + return registerArgs; +} + +// Register a Basename for the given Wallet. +async function registerBaseName(wallet, registerArgs) { + try { + const contractInvocation = await wallet.invokeContract({ + contractAddress: BaseNamesRegistrarControllerAddress, + method: "register", + abi: registrarABI, + args: registerArgs, + amount: 0.002, + assetId: Coinbase.assets.Eth, + }); + + await contractInvocation.wait(); + + console.log(`Successfully registered Basename ${registerArgs[0]} for wallet: `, wallet); + } catch (error) { + console.error(`Error registering a Basename for ${wallet}: `, error); + } +} + +// Fetch a funded Wallet and load its Seed. +async function fetchWalletAndLoadSeed(walletId, seedFilePath) { + try { + const wallet = await Wallet.fetch(walletId); + await wallet.loadSeed(seedFilePath); + + console.log(`Successfully loaded funded wallet: `, wallet); + return wallet; + } catch (error) { + console.error( + `Error loading funded wallet ${walletId} from seed file ${seedFilePath}: `, + error, + ); + } +} + +async () => { + try { + const { BASE_NAME, WALLET_ID, SEED_FILE_PATH } = process.env; + + // Manage CDP API Key for Coinbase SDK. + // Configure location to CDP API Key. + Coinbase.configureFromJson({ + filePath: `${os.homedir()}/Downloads/cdp_api_key.json`, + }); + + // Fetch funded Wallet. + const wallet = await fetchWalletAndLoadSeed(WALLET_ID, SEED_FILE_PATH); + const defaultAddress = await wallet.getDefaultAddress(); + + // Register Basename. + const registerArgs = createRegisterContractMethodArgs(BASE_NAME, defaultAddress.getId()); + await registerBaseName(wallet, registerArgs); + } catch (error) { + console.error(`Error in registering a Basename for my wallet: `, error); + } +}; diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index d2c22642..4d1a4b32 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -929,14 +929,7 @@ export class Wallet { if (!this.master) { throw new Error("Cannot derive key for Wallet without seed loaded"); } - const [networkPrefix] = this.model.network_id.split("-"); - /** - * TODO: Push this logic to the backend. - * TODO: Add unit tests for `#createAddress`. - */ - if (!["base", "ethereum", "polygon"].includes(networkPrefix)) { - throw new Error(`Unsupported network ID: ${this.model.network_id}`); - } + const derivedKey = this.master?.derive(this.addressPathPrefix + `/${index}`); if (!derivedKey?.privateKey) { throw new Error("Failed to derive key"); diff --git a/src/tests/wallet_test.ts b/src/tests/wallet_test.ts index af476d08..7454fb5c 100644 --- a/src/tests/wallet_test.ts +++ b/src/tests/wallet_test.ts @@ -902,20 +902,6 @@ describe("Wallet Class", () => { expect(() => newWallet.setSeed(``)).toThrow(ArgumentError); expect(() => newWallet.setSeed(`invalid-seed`)).toThrow(ArgumentError); }); - - it("should raise an error when creating a wallet with an invalid network", async () => { - const newWallet = Wallet.init( - { - ...walletModel, - network_id: "invalid_network_id", - }, - "", - ); - newWallet.setSeed(existingSeed); - await expect(newWallet.createAddress()).rejects.toThrow( - "Unsupported network ID: invalid_network_id", - ); - }); }); describe("#export", () => {