├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── prerelease.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc ├── .releaserc.cjs ├── .vscode ├── extensions.json ├── settings.json └── testing.code-snippets ├── LICENSE ├── README.md ├── commitlint.config.cjs ├── cspell.json ├── examples ├── c-chain │ ├── export.ts │ └── import.ts ├── chain_apis.ts ├── generate-keys.ts ├── p-chain │ ├── base.ts │ ├── convertSubnetToL1.ts │ ├── createChain.ts │ ├── createSubnet.ts │ ├── delegate.ts │ ├── export.ts │ ├── import.ts │ ├── increaseBalanceTx.ts │ ├── utils │ │ ├── addSignatureToAllCred.ts │ │ ├── etna-helper.ts │ │ └── random-node-id.ts │ └── validate.ts ├── utils │ ├── getChainIdFromContext.ts │ └── getEnvVars.ts ├── warp │ ├── l1ValidatorWeightMessage.ts │ └── registerL1ValidatorMessage.ts └── x-chain │ ├── export.ts │ └── import.ts ├── package.json ├── rollup.config.mjs ├── src ├── constants │ ├── codec.ts │ ├── conversion.ts │ ├── endpoints.ts │ ├── networkIDs.ts │ ├── node.ts │ ├── public-urls.ts │ └── zeroValue.ts ├── crypto │ ├── bls.test.ts │ ├── bls.ts │ ├── index.ts │ ├── secp256k1.test.ts │ └── secp256k1.ts ├── fixtures │ ├── avax.ts │ ├── codec.ts │ ├── common.ts │ ├── context.ts │ ├── evm.ts │ ├── info.ts │ ├── nft.ts │ ├── primitives.ts │ ├── pvm.ts │ ├── secp256k1.ts │ ├── transactions.ts │ ├── utils │ │ ├── bytesFor.ts │ │ ├── expectTx.ts │ │ ├── makeList.test.ts │ │ ├── makeList.ts │ │ ├── serializable.ts │ │ └── typeguards.ts │ ├── vms.ts │ └── warp.ts ├── index.ts ├── info │ ├── api.ts │ ├── index.ts │ └── model.ts ├── serializable │ ├── avax │ │ ├── avaxTx.ts │ │ ├── baseTx.test.ts │ │ ├── baseTx.ts │ │ ├── index.ts │ │ ├── signedTx.test.ts │ │ ├── signedTx.ts │ │ ├── transferableInput.test.ts │ │ ├── transferableInput.ts │ │ ├── transferableOp.test.ts │ │ ├── transferableOp.ts │ │ ├── transferableOutput.test.ts │ │ ├── transferableOutput.ts │ │ ├── typeGuards.ts │ │ ├── utxo.test.ts │ │ ├── utxo.ts │ │ ├── utxoId.test.ts │ │ └── utxoId.ts │ ├── avm │ │ ├── abstractTx.ts │ │ ├── baseTx.test.ts │ │ ├── baseTx.ts │ │ ├── codec.ts │ │ ├── createAssetTx.test.ts │ │ ├── createAssetTx.ts │ │ ├── exportTx.test.ts │ │ ├── exportTx.ts │ │ ├── importTx.test.ts │ │ ├── importTx.ts │ │ ├── index.ts │ │ ├── initialState.test.ts │ │ ├── initialState.ts │ │ ├── operationTx.test.ts │ │ ├── operationTx.ts │ │ ├── typeGuards.test.ts │ │ └── typeGuards.ts │ ├── codec │ │ ├── codec.test.ts │ │ ├── codec.ts │ │ ├── index.ts │ │ ├── manager.test.ts │ │ └── manager.ts │ ├── common │ │ └── types.ts │ ├── constants.ts │ ├── evm │ │ ├── abstractTx.ts │ │ ├── codec.ts │ │ ├── exportTx.test.ts │ │ ├── exportTx.ts │ │ ├── importTx.test.ts │ │ ├── importTx.ts │ │ ├── index.ts │ │ ├── input.test.ts │ │ ├── input.ts │ │ ├── output.test.ts │ │ ├── output.ts │ │ ├── typeGuards.test.ts │ │ └── typeGuards.ts │ ├── fxs │ │ ├── common │ │ │ ├── address.test.ts │ │ │ ├── address.ts │ │ │ ├── blsPublicKey.test.ts │ │ │ ├── blsPublicKey.ts │ │ │ ├── blsSignature.test.ts │ │ │ ├── blsSignature.ts │ │ │ ├── id.test.ts │ │ │ ├── id.ts │ │ │ ├── index.ts │ │ │ ├── nodeId.test.ts │ │ │ └── nodeId.ts │ │ ├── nft │ │ │ ├── credential.ts │ │ │ ├── index.ts │ │ │ ├── mintOperation.test.ts │ │ │ ├── mintOperation.ts │ │ │ ├── mintOutput.test.ts │ │ │ ├── mintOutput.ts │ │ │ ├── transferOperation.test.ts │ │ │ ├── transferOperation.ts │ │ │ ├── transferOutput.test.ts │ │ │ └── transferOutput.ts │ │ ├── pvm │ │ │ ├── L1Validator.test.ts │ │ │ ├── L1Validator.ts │ │ │ ├── index.ts │ │ │ ├── pChainOwner.test.ts │ │ │ └── pChainOwner.ts │ │ └── secp256k1 │ │ │ ├── credential.test.ts │ │ │ ├── credential.ts │ │ │ ├── index.ts │ │ │ ├── input.test.ts │ │ │ ├── input.ts │ │ │ ├── mintOperation.test.ts │ │ │ ├── mintOperation.ts │ │ │ ├── mintOutput.test.ts │ │ │ ├── mintOutput.ts │ │ │ ├── outputOwners.test.ts │ │ │ ├── outputOwners.ts │ │ │ ├── outputOwnersList.test.ts │ │ │ ├── outputOwnersList.ts │ │ │ ├── signature.test.ts │ │ │ ├── signature.ts │ │ │ ├── transferInput.test.ts │ │ │ ├── transferInput.ts │ │ │ ├── transferOutput.test.ts │ │ │ └── transferOutput.ts │ ├── index.ts │ ├── primitives │ │ ├── bigintpr.test.ts │ │ ├── bigintpr.ts │ │ ├── boolean.ts │ │ ├── byte.ts │ │ ├── bytes.test.ts │ │ ├── bytes.ts │ │ ├── index.ts │ │ ├── int.test.ts │ │ ├── int.ts │ │ ├── primatives.ts │ │ ├── short.test.ts │ │ ├── short.ts │ │ ├── stringpr.test.ts │ │ └── stringpr.ts │ └── pvm │ │ ├── abstractSubnetTx.ts │ │ ├── abstractTx.ts │ │ ├── addDelegatorTx.test.ts │ │ ├── addDelegatorTx.ts │ │ ├── addPermissionlessDelegatorTx.test.ts │ │ ├── addPermissionlessDelegatorTx.ts │ │ ├── addPermissionlessValidatorTx.test.ts │ │ ├── addPermissionlessValidatorTx.ts │ │ ├── addSubnetValidatorTx.test.ts │ │ ├── addSubnetValidatorTx.ts │ │ ├── addValidatorTx.test.ts │ │ ├── addValidatorTx.ts │ │ ├── advanceTimeTx.test.ts │ │ ├── advanceTimeTx.ts │ │ ├── baseTx.test.ts │ │ ├── baseTx.ts │ │ ├── codec.ts │ │ ├── convertSubnetToL1Tx.test.ts │ │ ├── convertSubnetToL1Tx.ts │ │ ├── createChainTx.test.ts │ │ ├── createChainTx.ts │ │ ├── createSubnetTx.test.ts │ │ ├── createSubnetTx.ts │ │ ├── disableL1ValidatorTx.test.ts │ │ ├── disableL1ValidatorTx.ts │ │ ├── exportTx.test.ts │ │ ├── exportTx.ts │ │ ├── importTx.test.ts │ │ ├── importTx.ts │ │ ├── increaseL1ValidatorBalance.test.ts │ │ ├── increaseL1ValidatorBalanceTx.ts │ │ ├── index.ts │ │ ├── proofOfPossession.test.ts │ │ ├── proofOfPossession.ts │ │ ├── registerL1Validator.test.ts │ │ ├── registerL1ValidatorTx.ts │ │ ├── removeSubnetValidator.test.ts │ │ ├── removeSubnetValidatorTx.ts │ │ ├── rewardValidatorTx.ts │ │ ├── setL1ValidatorWeightTx.test.ts │ │ ├── setL1ValidatorWeightTx.ts │ │ ├── signer.ts │ │ ├── stakeableLockIn.test.ts │ │ ├── stakeableLockIn.ts │ │ ├── stakeableLockOut.test.ts │ │ ├── stakeableLockOut.ts │ │ ├── subnetValidator.test.ts │ │ ├── subnetValidator.ts │ │ ├── transferSubnetOwnershipTx.test.ts │ │ ├── transferSubnetOwnershipTx.ts │ │ ├── transformSubnetTx.test.ts │ │ ├── transformSubnetTx.ts │ │ ├── typeGuards.test.ts │ │ ├── typeGuards.ts │ │ ├── validator.test.ts │ │ ├── validator.ts │ │ └── warp │ │ ├── addressedCall │ │ ├── addressedCallPayload.test.ts │ │ ├── addressedCallPayload.ts │ │ ├── index.ts │ │ ├── messages │ │ │ ├── l1ValidatorRegistrationMessage.test.ts │ │ │ ├── l1ValidatorRegistrationMessage.ts │ │ │ ├── l1ValidatorWeightMessage.test.ts │ │ │ ├── l1ValidatorWeightMessage.ts │ │ │ ├── registerL1ValidatorMessage.test.ts │ │ │ ├── registerL1ValidatorMessage.ts │ │ │ ├── subnetToL1ConversionMessage.test.ts │ │ │ └── subnetToL1ConversionMessage.ts │ │ └── utils │ │ │ ├── conversionData.test.ts │ │ │ ├── conversionData.ts │ │ │ ├── validatorData.test.ts │ │ │ └── validatorData.ts │ │ ├── codec.ts │ │ ├── index.ts │ │ ├── message.test.ts │ │ ├── message.ts │ │ ├── signature.test.ts │ │ ├── signature.ts │ │ ├── unsignedMessage.test.ts │ │ └── unsignedMessage.ts ├── signer │ ├── addTxSignatures.test.ts │ ├── addTxSignatures.ts │ └── index.ts ├── utils │ ├── UTXOSet │ │ ├── UTXOSet.ts │ │ ├── index.ts │ │ └── models.ts │ ├── addChecksum.ts │ ├── address.test.ts │ ├── address.ts │ ├── addressMap.test.ts │ ├── addressMap.ts │ ├── addressesFromBytes.ts │ ├── avaxToNAvax.ts │ ├── base58.test.ts │ ├── base58.ts │ ├── bigintMath.ts │ ├── buffer.test.ts │ ├── buffer.ts │ ├── builderUtils.ts │ ├── bytesCompare.test.ts │ ├── bytesCompare.ts │ ├── consolidate.test.ts │ ├── consolidate.ts │ ├── costs.ts │ ├── devutils.ts │ ├── getBurnedAmountByTx.test.ts │ ├── getBurnedAmountByTx.ts │ ├── getTransferableInputsByTx.test.ts │ ├── getTransferableInputsByTx.ts │ ├── getTransferableOutputsByTx.test.ts │ ├── getTransferableOutputsByTx.ts │ ├── getUtxoInfo.ts │ ├── index.ts │ ├── isEtnaEnabled.ts │ ├── models.ts │ ├── packTx.ts │ ├── removeDuplicateUTXOs.ts │ ├── serializeList.test.ts │ ├── serializeList.ts │ ├── sort.test.ts │ ├── sort.ts │ ├── struct.test.ts │ ├── struct.ts │ ├── transferableAmounts.ts │ ├── typeGuards.ts │ └── validateBurnedAmount │ │ ├── validateBurnedAmount.ts │ │ ├── validateDynamicBurnedAmount.test.ts │ │ ├── validateDynamicBurnedAmount.ts │ │ ├── validateStaticBurnedAmount.test.ts │ │ └── validateStaticBurnedAmount.ts └── vms │ ├── avm │ ├── api.ts │ ├── builder.test.ts │ ├── builder.ts │ ├── index.ts │ ├── models.ts │ └── utxoCalculationFns │ │ ├── index.ts │ │ └── useAvmAndCorethUTXOs.ts │ ├── common │ ├── apiModels.ts │ ├── avaxApi.ts │ ├── baseApi.ts │ ├── builder.ts │ ├── chainAPI.ts │ ├── defaultSpendOptions.ts │ ├── evmUnsignedTx.test.ts │ ├── evmUnsignedTx.ts │ ├── fees │ │ └── dimensions.ts │ ├── index.ts │ ├── models.ts │ ├── rpc.ts │ ├── transaction.ts │ ├── types.ts │ ├── unsignedTx.test.ts │ └── unsignedTx.ts │ ├── context │ ├── context.ts │ ├── index.ts │ └── model.ts │ ├── evm │ ├── api.ts │ ├── builder.test.ts │ ├── builder.ts │ ├── index.ts │ ├── model.ts │ └── privateModels.ts │ ├── index.ts │ ├── pvm │ ├── api.ts │ ├── etna-builder │ │ ├── builder.test.ts │ │ ├── builder.ts │ │ ├── index.ts │ │ ├── spend-reducers │ │ │ ├── errors.ts │ │ │ ├── fixtures │ │ │ │ └── reducers.ts │ │ │ ├── handleFeeAndChange.test.ts │ │ │ ├── handleFeeAndChange.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── useSpendableLockedUTXOs.test.ts │ │ │ ├── useSpendableLockedUTXOs.ts │ │ │ ├── useUnlockedUTXOs.test.ts │ │ │ ├── useUnlockedUTXOs.ts │ │ │ ├── verifyAssetsConsumed.test.ts │ │ │ ├── verifyAssetsConsumed.ts │ │ │ ├── verifyGasUsage.test.ts │ │ │ └── verifyGasUsage.ts │ │ ├── spend.test.ts │ │ ├── spend.ts │ │ ├── spendHelper.test.ts │ │ ├── spendHelper.ts │ │ └── utils │ │ │ └── feeForTesting.ts │ ├── index.ts │ ├── models.ts │ ├── privateModels.ts │ ├── txs │ │ └── fee │ │ │ ├── calculator.test.ts │ │ │ ├── calculator.ts │ │ │ ├── complexity.test.ts │ │ │ ├── complexity.ts │ │ │ ├── constants.ts │ │ │ ├── fixtures │ │ │ └── transactions.ts │ │ │ └── index.ts │ └── utxoCalculationFns │ │ ├── index.ts │ │ ├── useConsolidateOutputs.ts │ │ ├── useSpendableLockedUTXOs.test.ts │ │ ├── useSpendableLockedUTXOs.ts │ │ ├── useUnlockedUTXOs.test.ts │ │ └── useUnlockedUTXOs.ts │ └── utils │ ├── calculateSpend │ ├── calculateSpend.test.ts │ ├── calculateSpend.ts │ ├── index.ts │ ├── models.ts │ └── utils │ │ ├── index.ts │ │ └── verifySignaturesMatch.ts │ ├── consolidateOutputs.test.ts │ └── consolidateOutputs.ts ├── tsconfig.json ├── vitest.config.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | AVAX_PUBLIC_URL="https://api.avax-test.network" 2 | 3 | PRIVATE_KEY=... 4 | C_CHAIN_ADDRESS=0x... 5 | X_CHAIN_ADDRESS=X-fuji... 6 | P_CHAIN_ADDRESS=P-fuji... 7 | CORETH_ADDRESS=C-fuji... 8 | NODE_ID=NodeID-... 9 | BLS_PUBLIC_KEY=0x... 10 | BLS_SIGNATURE=0x... -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier', 11 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 12 | ], 13 | parser: '@typescript-eslint/parser', 14 | settings: {}, 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | ecmaVersion: 12, 20 | sourceType: 'module', 21 | // TODO: Why does uncommenting this remove the type imports? 22 | // project: ['./tsconfig.json'], 23 | }, 24 | plugins: ['@typescript-eslint'], 25 | rules: { 26 | '@typescript-eslint/consistent-type-imports': 'error', 27 | '@typescript-eslint/explicit-module-boundary-types': 'off', 28 | '@typescript-eslint/no-explicit-any': 'off', 29 | '@typescript-eslint/no-var-requires': 'error', 30 | // Uncomment prefer-readonly and parserOptions.project to apply the rule. 31 | // TODO: Uncomment both permanently after fixing the type import issue. 32 | // '@typescript-eslint/prefer-readonly': 'error', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: npm 8 | directory: "/" 9 | schedule: 10 | interval: weekly -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: PR checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | Lint-build-test: 11 | name: Lint, build and test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 22.x 20 | cache: yarn 21 | - name: Install dependencies 22 | run: yarn install --frozen-lockfile 23 | - name: Lint 24 | run: yarn lint:check 25 | - name: Typecheck 26 | run: yarn typecheck 27 | - name: Test 28 | run: yarn test 29 | - name: Build 30 | run: yarn build 31 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Prerelease 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Prerelease 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 22.x 19 | cache: yarn 20 | - name: Install dependencies 21 | run: yarn install --frozen-lockfile 22 | - name: Build library 23 | run: yarn build:prod 24 | - name: Release 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 29 | run: yarn run semantic-release 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - release 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 22.x 19 | cache: yarn 20 | - name: Install dependencies 21 | run: yarn install --frozen-lockfile 22 | - name: Build library 23 | run: yarn build:prod 24 | - name: Release 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 29 | run: yarn run semantic-release 30 | - name: Rebase master 31 | run: | 32 | git checkout master && 33 | git rebase release && 34 | git push origin master 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /node_modules 4 | /dist 5 | /coverage 6 | /yarn-error.log 7 | 8 | .idea 9 | 10 | stats.html 11 | .dccache 12 | 13 | todo.* 14 | .env -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | 5 | yarn build && npx lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "vitest.explorer" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.preferences.importModuleSpecifier": "relative", 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } 5 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'body-max-line-length': [0], 5 | 'footer-max-line-length': [0], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "dictionaries": [ 5 | "companies", 6 | "css", 7 | "en_us", 8 | "en-gb", 9 | "fullstack", 10 | "html", 11 | "lorem-ipsum", 12 | "node", 13 | "npm", 14 | "softwareTerms", 15 | "sql", 16 | "typescript" 17 | ], 18 | "ignorePaths": ["node_modules", "__generated__", "build", "dist", "out"], 19 | "ignoreRegExpList": ["/.*[0-9].*/"], 20 | "language": "en", 21 | "minWordLength": 5, 22 | "words": [ 23 | "amounter", 24 | "avalabs", 25 | "locktime", 26 | "permissioned", 27 | "stakeable", 28 | "unstakeable", 29 | "utxo", 30 | "utxos" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /examples/c-chain/export.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcProvider } from 'ethers'; 2 | import { addTxSignatures } from '../../src/signer'; 3 | import { bech32ToBytes, hexToBuffer } from '../../src/utils'; 4 | import { getContextFromURI } from '../../src/vms/context'; 5 | import { newExportTxFromBaseFee } from '../../src/vms/evm'; 6 | import { evmapi } from '../chain_apis'; 7 | import { getEnvVars } from '../utils/getEnvVars'; 8 | 9 | const main = async () => { 10 | const { AVAX_PUBLIC_URL, C_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } = 11 | getEnvVars(); 12 | 13 | const provider = new JsonRpcProvider(AVAX_PUBLIC_URL + '/ext/bc/C/rpc'); 14 | 15 | const context = await getContextFromURI(AVAX_PUBLIC_URL); 16 | const txCount = await provider.getTransactionCount(C_CHAIN_ADDRESS); 17 | const baseFee = await evmapi.getBaseFee(); 18 | const xAddressBytes = bech32ToBytes(X_CHAIN_ADDRESS); 19 | 20 | const tx = newExportTxFromBaseFee( 21 | context, 22 | baseFee / BigInt(1e9), 23 | BigInt(0.1 * 1e9), 24 | context.xBlockchainID, 25 | hexToBuffer(C_CHAIN_ADDRESS), 26 | [xAddressBytes], 27 | BigInt(txCount), 28 | ); 29 | 30 | await addTxSignatures({ 31 | unsignedTx: tx, 32 | privateKeys: [hexToBuffer(PRIVATE_KEY)], 33 | }); 34 | 35 | return evmapi.issueSignedTx(tx.getSignedTx()); 36 | }; 37 | 38 | main().then(console.log); 39 | -------------------------------------------------------------------------------- /examples/c-chain/import.ts: -------------------------------------------------------------------------------- 1 | import { addTxSignatures } from '../../src/signer'; 2 | import { bech32ToBytes, hexToBuffer } from '../../src/utils'; 3 | import { getContextFromURI } from '../../src/vms/context'; 4 | import { newImportTxFromBaseFee } from '../../src/vms/evm'; 5 | import { evmapi } from '../chain_apis'; 6 | import { getChainIdFromContext } from '../utils/getChainIdFromContext'; 7 | import { getEnvVars } from '../utils/getEnvVars'; 8 | 9 | const main = async (sourceChain: 'X' | 'P') => { 10 | const { AVAX_PUBLIC_URL, C_CHAIN_ADDRESS, PRIVATE_KEY, CORETH_ADDRESS } = 11 | getEnvVars(); 12 | 13 | const baseFee = await evmapi.getBaseFee(); 14 | const context = await getContextFromURI(AVAX_PUBLIC_URL); 15 | 16 | const { utxos } = await evmapi.getUTXOs({ 17 | sourceChain, 18 | addresses: [CORETH_ADDRESS], 19 | }); 20 | 21 | const tx = newImportTxFromBaseFee( 22 | context, 23 | hexToBuffer(C_CHAIN_ADDRESS), 24 | [bech32ToBytes(CORETH_ADDRESS)], 25 | utxos, 26 | getChainIdFromContext(sourceChain, context), 27 | baseFee / BigInt(1e9), 28 | ); 29 | 30 | await addTxSignatures({ 31 | unsignedTx: tx, 32 | privateKeys: [hexToBuffer(PRIVATE_KEY)], 33 | }); 34 | 35 | return evmapi.issueSignedTx(tx.getSignedTx()); 36 | }; 37 | 38 | main('X').then(console.log); 39 | -------------------------------------------------------------------------------- /examples/chain_apis.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { config } from 'dotenv'; 3 | 4 | config(); 5 | 6 | import { AVMApi } from '../src/vms/avm/api'; 7 | import { EVMApi } from '../src/vms/evm/api'; 8 | import { PVMApi } from '../src/vms/pvm/api'; 9 | 10 | // polyfill fetch if it doesnt exist in the global space 11 | global.fetch = global.fetch || fetch; 12 | 13 | export const evmapi = new EVMApi(process.env.AVAX_PUBLIC_URL); 14 | export const avmapi = new AVMApi(process.env.AVAX_PUBLIC_URL); 15 | export const pvmapi = new PVMApi(process.env.AVAX_PUBLIC_URL); 16 | -------------------------------------------------------------------------------- /examples/p-chain/base.ts: -------------------------------------------------------------------------------- 1 | import { TransferableOutput, addTxSignatures, pvm, utils } from '../../src'; 2 | import { getEnvVars } from '../utils/getEnvVars'; 3 | import { setupEtnaExample } from './utils/etna-helper'; 4 | 5 | /** 6 | * The amount of AVAX to send to self. 7 | */ 8 | const SEND_AVAX_AMOUNT: number = 0.001; 9 | 10 | const main = async () => { 11 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); 12 | 13 | const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); 14 | 15 | const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); 16 | 17 | const tx = pvm.newBaseTx( 18 | { 19 | feeState, 20 | fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], 21 | outputs: [ 22 | TransferableOutput.fromNative( 23 | context.avaxAssetID, 24 | BigInt(SEND_AVAX_AMOUNT * 1e9), 25 | [utils.bech32ToBytes(P_CHAIN_ADDRESS)], 26 | ), 27 | ], 28 | utxos, 29 | }, 30 | context, 31 | ); 32 | 33 | await addTxSignatures({ 34 | unsignedTx: tx, 35 | privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], 36 | }); 37 | 38 | return pvmApi.issueSignedTx(tx.getSignedTx()); 39 | }; 40 | 41 | main().then(console.log); 42 | -------------------------------------------------------------------------------- /examples/p-chain/createSubnet.ts: -------------------------------------------------------------------------------- 1 | import { addTxSignatures, pvm, utils } from '../../src'; 2 | import { getEnvVars } from '../utils/getEnvVars'; 3 | import { setupEtnaExample } from './utils/etna-helper'; 4 | 5 | const createSubnetTxExample = async () => { 6 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); 7 | 8 | const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); 9 | 10 | const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); 11 | 12 | const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS); 13 | 14 | const tx = pvm.newCreateSubnetTx( 15 | { 16 | feeState, 17 | fromAddressesBytes: [testPAddr], 18 | utxos, 19 | subnetOwners: [testPAddr], 20 | }, 21 | context, 22 | ); 23 | 24 | await addTxSignatures({ 25 | unsignedTx: tx, 26 | privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], 27 | }); 28 | 29 | return pvmApi.issueSignedTx(tx.getSignedTx()); 30 | }; 31 | 32 | createSubnetTxExample().then(console.log); 33 | -------------------------------------------------------------------------------- /examples/p-chain/export.ts: -------------------------------------------------------------------------------- 1 | import { TransferableOutput, addTxSignatures, pvm, utils } from '../../src'; 2 | import { getEnvVars } from '../utils/getEnvVars'; 3 | import { setupEtnaExample } from './utils/etna-helper'; 4 | 5 | const AMOUNT_TO_EXPORT_AVAX: number = 0.001; 6 | 7 | const main = async () => { 8 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } = 9 | getEnvVars(); 10 | 11 | const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); 12 | 13 | const { utxos } = await pvmApi.getUTXOs({ 14 | addresses: [P_CHAIN_ADDRESS], 15 | }); 16 | 17 | const exportTx = pvm.newExportTx( 18 | { 19 | destinationChainId: context.xBlockchainID, 20 | feeState, 21 | fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], 22 | outputs: [ 23 | TransferableOutput.fromNative( 24 | context.avaxAssetID, 25 | BigInt(AMOUNT_TO_EXPORT_AVAX * 1e9), 26 | [utils.bech32ToBytes(X_CHAIN_ADDRESS)], 27 | ), 28 | ], 29 | utxos, 30 | }, 31 | context, 32 | ); 33 | 34 | await addTxSignatures({ 35 | unsignedTx: exportTx, 36 | privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], 37 | }); 38 | 39 | return pvmApi.issueSignedTx(exportTx.getSignedTx()); 40 | }; 41 | 42 | main().then(console.log); 43 | -------------------------------------------------------------------------------- /examples/p-chain/import.ts: -------------------------------------------------------------------------------- 1 | import { addTxSignatures, pvm, utils } from '../../src'; 2 | import { getEnvVars } from '../utils/getEnvVars'; 3 | import { setupEtnaExample } from './utils/etna-helper'; 4 | 5 | const main = async () => { 6 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } = 7 | getEnvVars(); 8 | 9 | const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); 10 | 11 | const { utxos } = await pvmApi.getUTXOs({ 12 | sourceChain: 'X', 13 | addresses: [P_CHAIN_ADDRESS], 14 | }); 15 | 16 | const importTx = pvm.newImportTx( 17 | { 18 | feeState, 19 | fromAddressesBytes: [utils.bech32ToBytes(X_CHAIN_ADDRESS)], 20 | sourceChainId: context.xBlockchainID, 21 | toAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], 22 | utxos, 23 | }, 24 | context, 25 | ); 26 | 27 | await addTxSignatures({ 28 | unsignedTx: importTx, 29 | privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], 30 | }); 31 | 32 | return pvmApi.issueSignedTx(importTx.getSignedTx()); 33 | }; 34 | 35 | main().then(console.log); 36 | -------------------------------------------------------------------------------- /examples/p-chain/increaseBalanceTx.ts: -------------------------------------------------------------------------------- 1 | import { addTxSignatures, pvm, utils } from '../../src'; 2 | import { setupEtnaExample } from './utils/etna-helper'; 3 | import { getEnvVars } from '../utils/getEnvVars'; 4 | 5 | const BALANCE_AVAX: number = 1; 6 | const VALIDATION_ID: string = ''; 7 | 8 | const increaseBalanceTx = async () => { 9 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); 10 | const { context, feeState, pvmApi } = await setupEtnaExample(AVAX_PUBLIC_URL); 11 | 12 | const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); 13 | 14 | const testPAddr = utils.bech32ToBytes(P_CHAIN_ADDRESS); 15 | 16 | const unsignedTx = pvm.newIncreaseL1ValidatorBalanceTx( 17 | { 18 | balance: BigInt(BALANCE_AVAX * 1e9), 19 | feeState, 20 | fromAddressesBytes: [testPAddr], 21 | utxos, 22 | validationId: VALIDATION_ID, 23 | }, 24 | context, 25 | ); 26 | 27 | await addTxSignatures({ 28 | unsignedTx, 29 | privateKeys: [utils.hexToBuffer(PRIVATE_KEY)], 30 | }); 31 | 32 | return pvmApi.issueSignedTx(unsignedTx.getSignedTx()); 33 | }; 34 | 35 | await increaseBalanceTx().then(console.log); 36 | -------------------------------------------------------------------------------- /examples/p-chain/utils/addSignatureToAllCred.ts: -------------------------------------------------------------------------------- 1 | import { secp256k1, type UnsignedTx } from '../../../src'; 2 | 3 | export const addSigToAllCreds = async ( 4 | unsignedTx: UnsignedTx, 5 | privateKey: Uint8Array, 6 | ) => { 7 | const unsignedBytes = unsignedTx.toBytes(); 8 | const publicKey = secp256k1.getPublicKey(privateKey); 9 | 10 | if (!unsignedTx.hasPubkey(publicKey)) { 11 | return; 12 | } 13 | const signature = await secp256k1.sign(unsignedBytes, privateKey); 14 | 15 | for (let i = 0; i < unsignedTx.getCredentials().length; i++) { 16 | unsignedTx.addSignatureAt(signature, i, 0); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /examples/p-chain/utils/etna-helper.ts: -------------------------------------------------------------------------------- 1 | import { Context, info, pvm } from '../../../src'; 2 | import type { FeeState } from '../../../src/vms/pvm'; 3 | 4 | export const setupEtnaExample = async ( 5 | uri: string, 6 | ): Promise<{ 7 | context: Context.Context; 8 | feeState: FeeState; 9 | pvmApi: pvm.PVMApi; 10 | }> => { 11 | const context = await Context.getContextFromURI(uri); 12 | const pvmApi = new pvm.PVMApi(uri); 13 | const feeState = await pvmApi.getFeeState(); 14 | 15 | const infoApi = new info.InfoApi(uri); 16 | 17 | const { etnaTime } = await infoApi.getUpgradesInfo(); 18 | 19 | const etnaDateTime = new Date(etnaTime); 20 | const now = new Date(); 21 | 22 | if (etnaDateTime >= now) { 23 | throw new Error( 24 | `Etna upgrade is not enabled. Upgrade time: ${etnaDateTime}`, 25 | ); 26 | } 27 | 28 | return { 29 | context, 30 | feeState, 31 | pvmApi, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /examples/p-chain/utils/random-node-id.ts: -------------------------------------------------------------------------------- 1 | import { base58check } from '../../../../src/utils'; 2 | 3 | export const getRandomNodeId = (): string => { 4 | const buffer = new Uint8Array(20); 5 | const randomBuffer = crypto.getRandomValues(buffer); 6 | 7 | const nodeId = `NodeID-${base58check.encode(randomBuffer)}`; 8 | 9 | return nodeId; 10 | }; 11 | -------------------------------------------------------------------------------- /examples/utils/getChainIdFromContext.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '../../src/vms/context'; 2 | 3 | export function getChainIdFromContext( 4 | sourceChain: 'X' | 'P' | 'C', 5 | context: Context, 6 | ) { 7 | return sourceChain === 'C' 8 | ? context.cBlockchainID 9 | : sourceChain === 'P' 10 | ? context.pBlockchainID 11 | : context.xBlockchainID; 12 | } 13 | -------------------------------------------------------------------------------- /examples/x-chain/export.ts: -------------------------------------------------------------------------------- 1 | import { TransferableOutput } from '../../src/serializable/avax'; 2 | import { addTxSignatures } from '../../src/signer'; 3 | import { bech32ToBytes, hexToBuffer } from '../../src/utils'; 4 | import { newExportTx } from '../../src/vms/avm'; 5 | import { getContextFromURI } from '../../src/vms/context'; 6 | import { avmapi } from '../chain_apis'; 7 | import { getChainIdFromContext } from '../utils/getChainIdFromContext'; 8 | import { getEnvVars } from '../utils/getEnvVars'; 9 | 10 | const main = async () => { 11 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } = 12 | getEnvVars(); 13 | 14 | const context = await getContextFromURI(AVAX_PUBLIC_URL); 15 | 16 | const { utxos } = await avmapi.getUTXOs({ 17 | addresses: [X_CHAIN_ADDRESS], 18 | }); 19 | 20 | const tx = newExportTx( 21 | context, 22 | getChainIdFromContext('P', context), 23 | [bech32ToBytes(X_CHAIN_ADDRESS)], 24 | utxos, 25 | [ 26 | TransferableOutput.fromNative(context.avaxAssetID, BigInt(0.1 * 1e9), [ 27 | bech32ToBytes(P_CHAIN_ADDRESS), 28 | ]), 29 | ], 30 | ); 31 | 32 | await addTxSignatures({ 33 | unsignedTx: tx, 34 | privateKeys: [hexToBuffer(PRIVATE_KEY)], 35 | }); 36 | 37 | return avmapi.issueSignedTx(tx.getSignedTx()); 38 | }; 39 | 40 | main().then(console.log); 41 | -------------------------------------------------------------------------------- /examples/x-chain/import.ts: -------------------------------------------------------------------------------- 1 | import { addTxSignatures } from '../../src/signer'; 2 | import { bech32ToBytes, hexToBuffer } from '../../src/utils'; 3 | import { newImportTx } from '../../src/vms/avm'; 4 | import { getContextFromURI } from '../../src/vms/context'; 5 | import { avmapi } from '../chain_apis'; 6 | import { getChainIdFromContext } from '../utils/getChainIdFromContext'; 7 | import { getEnvVars } from '../utils/getEnvVars'; 8 | 9 | export const main = async () => { 10 | const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } = 11 | getEnvVars(); 12 | 13 | const context = await getContextFromURI(AVAX_PUBLIC_URL); 14 | 15 | const { utxos } = await avmapi.getUTXOs({ 16 | sourceChain: 'P', 17 | addresses: [X_CHAIN_ADDRESS], 18 | }); 19 | 20 | const tx = newImportTx( 21 | context, 22 | getChainIdFromContext('P', context), 23 | utxos, 24 | [bech32ToBytes(X_CHAIN_ADDRESS)], 25 | [bech32ToBytes(P_CHAIN_ADDRESS)], 26 | ); 27 | 28 | await addTxSignatures({ 29 | unsignedTx: tx, 30 | privateKeys: [hexToBuffer(PRIVATE_KEY)], 31 | }); 32 | 33 | return avmapi.issueSignedTx(tx.getSignedTx()); 34 | }; 35 | 36 | main().then(console.log); 37 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 2 | import filesize from 'rollup-plugin-filesize'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | import terser from '@rollup/plugin-terser'; 5 | 6 | export default { 7 | input: './src/index.ts', 8 | external: [ 9 | '@noble/hashes/ripemd160', 10 | '@noble/hashes/sha256', 11 | '@noble/hashes/utils', 12 | '@scure/base', 13 | ], // we don't want these dependencies bundled in the dist folder 14 | output: [ 15 | { 16 | file: 'dist/index.cjs', 17 | format: 'cjs', 18 | plugins: [terser()], 19 | sourcemap: process.env.BUILD === 'production' ? false : true, 20 | }, 21 | { 22 | file: 'dist/es/index.js', 23 | format: 'esm', 24 | plugins: [terser()], 25 | sourcemap: process.env.BUILD === 'production' ? false : true, 26 | }, 27 | ], 28 | plugins: [filesize(), nodeResolve(), typescript()], 29 | }; 30 | -------------------------------------------------------------------------------- /src/constants/codec.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_CODEC_VERSION = 0; 2 | -------------------------------------------------------------------------------- /src/constants/conversion.ts: -------------------------------------------------------------------------------- 1 | export const CorethToEVMRatio = BigInt(1e9); 2 | -------------------------------------------------------------------------------- /src/constants/endpoints.ts: -------------------------------------------------------------------------------- 1 | export const MAINNET_PUBLIC_API_BASE_URL = 'https://api.avax.network'; 2 | export const TESTNET_PUBLIC_API_BASE_URL = 'https://api.avax-test.network'; 3 | 4 | export const C_CHAIN_ENDPOINT = '/ext/bc/C/rpc'; 5 | export const X_CHAIN_ENDPOINT = '/ext/bc/X'; 6 | export const P_CHAIN_ENDPOINT = '/ext/bc/P'; 7 | -------------------------------------------------------------------------------- /src/constants/networkIDs.ts: -------------------------------------------------------------------------------- 1 | import { Id } from '../serializable/fxs/common'; 2 | 3 | export const PrimaryNetworkID = new Id(new Uint8Array(32)); 4 | export const PlatformChainID = new Id(new Uint8Array(32)); 5 | 6 | export const MainnetName = 'mainnet'; 7 | export const CascadeName = 'cascade'; 8 | export const DenaliName = 'denali'; 9 | export const EverestName = 'everest'; 10 | export const FujiName = 'fuji'; 11 | export const TestnetName = 'testnet'; 12 | export const UnitTestName = 'testing'; 13 | export const LocalName = 'local'; 14 | 15 | export const MainnetID = 1; 16 | export const CascadeID = 2; 17 | export const DenaliID = 3; 18 | export const EverestID = 4; 19 | export const FujiID = 5; 20 | 21 | export const TestnetID = FujiID; 22 | export const UnitTestID = 10; 23 | export const LocalID = 12345; 24 | 25 | export const MainnetHRP = 'avax'; 26 | export const CascadeHRP = 'cascade'; 27 | export const DenaliHRP = 'denali'; 28 | export const EverestHRP = 'everest'; 29 | export const FujiHRP = 'fuji'; 30 | export const UnitTestHRP = 'testing'; 31 | export const LocalHRP = 'local'; 32 | export const FallbackHRP = 'custom'; 33 | 34 | export const NetworkIDToHRP = { 35 | [MainnetID]: MainnetHRP, 36 | [CascadeID]: CascadeHRP, 37 | [DenaliID]: DenaliHRP, 38 | [EverestID]: EverestHRP, 39 | [FujiID]: FujiHRP, 40 | [UnitTestID]: UnitTestHRP, 41 | [LocalID]: LocalHRP, 42 | }; 43 | 44 | /** 45 | * Returns the human readbale part for a bech32 string given the network ID. 46 | * @param networkID 47 | */ 48 | export const getHRP = (networkID: number): string => { 49 | return NetworkIDToHRP[networkID] ?? FallbackHRP; 50 | }; 51 | -------------------------------------------------------------------------------- /src/constants/node.ts: -------------------------------------------------------------------------------- 1 | export const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); 2 | -------------------------------------------------------------------------------- /src/constants/public-urls.ts: -------------------------------------------------------------------------------- 1 | export const AVAX_PUBLIC_URL = 'https://api.avax.network'; 2 | export const AVAX_PUBLIC_URL_FUJI = 'https://api.avax-test.network'; 3 | -------------------------------------------------------------------------------- /src/constants/zeroValue.ts: -------------------------------------------------------------------------------- 1 | import { Id } from '../serializable/fxs/common'; 2 | import { OutputOwners } from '../serializable/fxs/secp256k1'; 3 | import { 4 | SepkSignatureLength, 5 | Signature, 6 | } from '../serializable/fxs/secp256k1/signature'; 7 | import { BigIntPr, Int } from '../serializable/primitives'; 8 | 9 | export const zeroOutputOwners = new OutputOwners( 10 | new BigIntPr(0n), 11 | new Int(0), 12 | [], 13 | ); 14 | 15 | export const emptyId = new Id(new Uint8Array(32)); 16 | export const emptySignature = new Signature( 17 | new Uint8Array(Array(SepkSignatureLength).fill(0)), 18 | ); 19 | -------------------------------------------------------------------------------- /src/crypto/index.ts: -------------------------------------------------------------------------------- 1 | export * as secp256k1 from './secp256k1'; 2 | export * as bls from './bls'; 3 | -------------------------------------------------------------------------------- /src/fixtures/codec.ts: -------------------------------------------------------------------------------- 1 | import { getAVMManager } from '../serializable/avm/codec'; 2 | import { codec as EVMCodec } from '../serializable/evm/codec'; 3 | import { Short } from '../serializable/primitives'; 4 | import { codec } from '../serializable/pvm/codec'; 5 | import { codec as WarpCodec } from '../serializable/pvm/warp/codec'; 6 | 7 | // Check for circular imports in the fx type 8 | // registries if tests are throwing errors 9 | 10 | export const testManager = getAVMManager; 11 | 12 | export const testCodec = () => testManager().getCodecForVersion(new Short(0)); 13 | 14 | export const testPVMCodec = () => codec; 15 | 16 | export const testEVMCodec = () => EVMCodec; 17 | 18 | export const testWarpCodec = () => WarpCodec; 19 | -------------------------------------------------------------------------------- /src/fixtures/context.ts: -------------------------------------------------------------------------------- 1 | import { createDimensions } from '../vms/common/fees/dimensions'; 2 | import type { Context } from '../vms/context'; 3 | 4 | export const testContext: Context = { 5 | xBlockchainID: '2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM', 6 | pBlockchainID: '11111111111111111111111111111111LpoYY', 7 | cBlockchainID: '2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5', 8 | avaxAssetID: 'FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z', 9 | baseTxFee: 1000000n, 10 | createAssetTxFee: 10000000n, 11 | networkID: 1, 12 | hrp: 'avax', 13 | platformFeeConfig: { 14 | weights: createDimensions({ 15 | bandwidth: 1, 16 | dbRead: 1, 17 | dbWrite: 1, 18 | compute: 1, 19 | }), 20 | maxCapacity: 1_000_000n, 21 | maxPerSecond: 1_000n, 22 | targetPerSecond: 500n, 23 | minPrice: 1n, 24 | excessConversionConstant: 5_000n, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/fixtures/info.ts: -------------------------------------------------------------------------------- 1 | import type { GetUpgradesInfoResponse } from '../info'; 2 | 3 | export const upgradesInfo: GetUpgradesInfoResponse = { 4 | apricotPhase1Time: '2020-12-05T05:00:00Z', 5 | apricotPhase2Time: '2020-12-05T05:00:00Z', 6 | apricotPhase3Time: '2020-12-05T05:00:00Z', 7 | apricotPhase4Time: '2020-12-05T05:00:00Z', 8 | apricotPhase4MinPChainHeight: 0, 9 | apricotPhase5Time: '2020-12-05T05:00:00Z', 10 | apricotPhasePre6Time: '2020-12-05T05:00:00Z', 11 | apricotPhase6Time: '2020-12-05T05:00:00Z', 12 | apricotPhasePost6Time: '2020-12-05T05:00:00Z', 13 | banffTime: '2020-12-05T05:00:00Z', 14 | cortinaTime: '2020-12-05T05:00:00Z', 15 | cortinaXChainStopVertexID: '11111111111111111111111111111111LpoYY', 16 | durangoTime: '2020-12-05T05:00:00Z', 17 | etnaTime: '2020-12-05T05:00:00Z', 18 | }; 19 | -------------------------------------------------------------------------------- /src/fixtures/nft.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MintOperation, 3 | MintOutput, 4 | TransferOutput, 5 | } from '../serializable/fxs/nft'; 6 | import { bytes, bytesBytes, int, intBytes } from './primitives'; 7 | import { TransferOperation } from '../serializable/fxs/nft/transferOperation'; 8 | import { concatBytes } from '../utils/buffer'; 9 | import { 10 | input, 11 | inputBytes, 12 | outputOwner, 13 | outputOwnerBytes, 14 | outputOwnersList, 15 | outputOwnersListBytes, 16 | } from './secp256k1'; 17 | 18 | // https://docs.avax.network/specs/avm-transaction-serialization/#nft-mint-output-example 19 | export const mintOutputBytes = () => 20 | concatBytes(intBytes(), outputOwnerBytes()); 21 | 22 | export const mintOutput = () => new MintOutput(int(), outputOwner()); 23 | 24 | // https://docs.avax.network/specs/avm-transaction-serialization/#nft-transfer-output-example 25 | export const transferOutputBytes = () => 26 | concatBytes(intBytes(), bytesBytes(), outputOwnerBytes()); 27 | 28 | export const transferOutput = () => 29 | new TransferOutput(int(), bytes(), outputOwner()); 30 | 31 | export const mintOperationBytes = () => 32 | concatBytes(inputBytes(), intBytes(), bytesBytes(), outputOwnersListBytes()); 33 | export const mintOperation = () => 34 | new MintOperation(input(), int(), bytes(), outputOwnersList()); 35 | 36 | export const transferOperationBytes = () => 37 | concatBytes(inputBytes(), transferOutputBytes()); 38 | 39 | export const transferOperation = () => 40 | new TransferOperation(input(), transferOutput()); 41 | -------------------------------------------------------------------------------- /src/fixtures/utils/bytesFor.ts: -------------------------------------------------------------------------------- 1 | import { Int } from '../../serializable/primitives/int'; 2 | 3 | export const bytesForInt = (num: number): Uint8Array => new Int(num).toBytes(); 4 | -------------------------------------------------------------------------------- /src/fixtures/utils/expectTx.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest'; 2 | 3 | export const expectTxs = (result: any, expected: any) => { 4 | expect(JSON.stringify(result, null, 2)).toEqual( 5 | JSON.stringify(expected, null, 2), 6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /src/fixtures/utils/makeList.test.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { address, addressBytes } from '../common'; 5 | import { bytesForInt } from './bytesFor'; 6 | import { makeList, makeListBytes } from './makeList'; 7 | 8 | describe('makeList', () => { 9 | it('make lists', () => { 10 | expect(makeList(address)()).toEqual([address(), address()]); 11 | }); 12 | 13 | it('make lists bytes', () => { 14 | expect(makeListBytes(addressBytes)()).toEqual( 15 | concatBytes(bytesForInt(2), addressBytes(), addressBytes()), 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/fixtures/utils/makeList.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import type { Serializable } from '../../serializable/common/types'; 3 | import { bytesForInt } from './bytesFor'; 4 | 5 | export const makeList = 6 | (single: () => T) => 7 | (): T[] => 8 | [single(), single()]; 9 | 10 | export const makeListBytes = (single: () => Uint8Array) => () => 11 | concatBytes(bytesForInt(2), single(), single()); 12 | -------------------------------------------------------------------------------- /src/fixtures/utils/serializable.ts: -------------------------------------------------------------------------------- 1 | import type { Codec } from '../../serializable/codec'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import type { 5 | Serializable, 6 | SerializableStatic, 7 | } from '../../serializable/common/types'; 8 | import { testCodec } from '../codec'; 9 | 10 | export const testSerialization = ( 11 | name: string, 12 | entity: SerializableStatic, 13 | entityFixture: () => Serializable, 14 | bytesFixture: () => Uint8Array, 15 | codec: () => Codec = testCodec, 16 | ) => { 17 | describe(name, () => { 18 | it('deserializes correctly', () => { 19 | const [output, remainder] = entity.fromBytes(bytesFixture(), codec()); 20 | expect(JSON.stringify(output)).toBe(JSON.stringify(entityFixture())); 21 | expect(remainder).toStrictEqual(new Uint8Array()); 22 | }); 23 | }); 24 | 25 | describe(name, () => { 26 | it('serializes correctly', () => { 27 | expect(entityFixture().toBytes(codec())).toStrictEqual(bytesFixture()); 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/fixtures/utils/typeguards.ts: -------------------------------------------------------------------------------- 1 | export type TypeChecker = (object: any) => boolean; 2 | 3 | export function checkAllGuards(object: any, guards: TypeChecker[]) { 4 | return guards.map((checker) => checker(object)); 5 | } 6 | 7 | export function countGuardPasses(object: any, guards: TypeChecker[]) { 8 | return checkAllGuards(object, guards).filter((val) => val).length; 9 | } 10 | 11 | export function onlyChecksOneGuard(object: any, guards: TypeChecker[]) { 12 | return countGuardPasses(object, guards) === 1; 13 | } 14 | -------------------------------------------------------------------------------- /src/fixtures/vms.ts: -------------------------------------------------------------------------------- 1 | import { secp256k1 } from '../crypto'; 2 | import { hexToBuffer } from '../utils'; 3 | 4 | export const testPrivateKey1 = hexToBuffer( 5 | '9c0523e7611e62f5dca291ad335e950db076c5ee31c4107355abde0d357bbd29', 6 | ); 7 | export const testPublicKey1 = secp256k1.getPublicKey(testPrivateKey1); 8 | export const testAddress1 = secp256k1.publicKeyBytesToAddress(testPublicKey1); 9 | export const testEthAddress1 = secp256k1.publicKeyToEthAddress(testPublicKey1); 10 | 11 | export const testPrivateKey2 = hexToBuffer( 12 | 'd11e7aa633eb15682bc2456d399c2a9861c82e0a308dbfd4d3a51ffa972f2b62', 13 | ); 14 | export const testPublicKey2 = secp256k1.getPublicKey(testPrivateKey2); 15 | export const testAddress2 = secp256k1.publicKeyBytesToAddress(testPublicKey2); 16 | export const testEthAddress2 = secp256k1.publicKeyToEthAddress(testPublicKey2); 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as networkIDs from './constants/networkIDs'; 2 | export * from './serializable'; 3 | export { Utxo } from './serializable/avax/utxo'; 4 | export * from './signer'; 5 | export * as utils from './utils'; 6 | export * from './vms'; 7 | export * as info from './info'; 8 | export * from './crypto'; 9 | -------------------------------------------------------------------------------- /src/info/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './model'; 3 | -------------------------------------------------------------------------------- /src/serializable/avax/avaxTx.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '../../vms/common/transaction'; 2 | import type { BaseTx } from './baseTx'; 3 | import type { TransferableInput } from './transferableInput'; 4 | 5 | export abstract class AvaxTx extends Transaction { 6 | abstract baseTx?: BaseTx; 7 | 8 | getInputs(): readonly TransferableInput[] { 9 | return this.baseTx?.inputs ?? []; 10 | } 11 | getBlockchainId() { 12 | return this.baseTx?.BlockchainId.toString() ?? ''; 13 | } 14 | 15 | getSigIndices(): number[][] { 16 | return this.getInputs() 17 | .map((input) => { 18 | return input.sigIndicies(); 19 | }) 20 | .filter((indicies): indicies is number[] => indicies !== undefined); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/serializable/avax/baseTx.test.ts: -------------------------------------------------------------------------------- 1 | import { baseTx, baseTxbytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { BaseTx } from './baseTx'; 4 | 5 | testSerialization('BaseTx', BaseTx, baseTx, baseTxbytes); 6 | -------------------------------------------------------------------------------- /src/serializable/avax/index.ts: -------------------------------------------------------------------------------- 1 | export { AvaxTx } from './avaxTx'; 2 | export { BaseTx } from './baseTx'; 3 | export * from './signedTx'; 4 | export { TransferableInput } from './transferableInput'; 5 | export { TransferableOutput } from './transferableOutput'; 6 | export * from './typeGuards'; 7 | export { UTXOID } from './utxoId'; 8 | -------------------------------------------------------------------------------- /src/serializable/avax/signedTx.test.ts: -------------------------------------------------------------------------------- 1 | import { signedTx, signedTxBytes } from '../../fixtures/avax'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { getAVMManager } from '../avm/codec'; 5 | import { SignedTx } from './signedTx'; 6 | 7 | describe('SignedTx', () => { 8 | it('deserializes correctly', () => { 9 | const output = getAVMManager().unpack(signedTxBytes(), SignedTx); 10 | expect(JSON.stringify(output)).toBe(JSON.stringify(signedTx())); 11 | }); 12 | }); 13 | 14 | describe('SignedTx', () => { 15 | it('serializes correctly', () => { 16 | expect(signedTx().toBytes()).toStrictEqual(signedTxBytes()); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/serializable/avax/transferableOp.test.ts: -------------------------------------------------------------------------------- 1 | import { transferableOp, transferableOpBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { TransferableOp } from './transferableOp'; 4 | 5 | testSerialization( 6 | 'TransferableOp', 7 | TransferableOp, 8 | transferableOp, 9 | transferableOpBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/avax/transferableOp.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import { packList, toListStruct } from '../../utils/serializeList'; 3 | import { pack, unpack } from '../../utils/struct'; 4 | import { Codec } from '../codec/codec'; 5 | import type { Serializable } from '../common/types'; 6 | import { serializable } from '../common/types'; 7 | import { Id } from '../fxs/common/id'; 8 | import { UTXOID } from './utxoId'; 9 | import { TypeSymbols } from '../constants'; 10 | 11 | /** 12 | * @see https://docs.avax.network/specs/avm-transaction-serialization#transferable-op 13 | */ 14 | @serializable() 15 | export class TransferableOp { 16 | _type = TypeSymbols.TransferableOp; 17 | 18 | constructor( 19 | private readonly assetId: Id, 20 | private readonly UTXOId: UTXOID[], 21 | private readonly transferOp: Serializable, 22 | ) {} 23 | 24 | static fromBytes( 25 | bytes: Uint8Array, 26 | codec: Codec, 27 | ): [TransferableOp, Uint8Array] { 28 | const [assetId, utxoID, transferOp, remaining] = unpack( 29 | bytes, 30 | [Id, toListStruct(UTXOID), Codec], 31 | codec, 32 | ); 33 | 34 | return [new TransferableOp(assetId, utxoID, transferOp), remaining]; 35 | } 36 | 37 | toBytes(codec: Codec) { 38 | return concatBytes( 39 | pack([this.assetId], codec), 40 | packList(this.UTXOId, codec), 41 | codec.PackPrefix(this.transferOp), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/serializable/avax/transferableOutput.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | transferableOutput, 3 | transferableOutputBytes, 4 | } from '../../fixtures/avax'; 5 | import { testSerialization } from '../../fixtures/utils/serializable'; 6 | import { TransferableOutput } from './transferableOutput'; 7 | 8 | testSerialization( 9 | 'TransferableInput', 10 | TransferableOutput, 11 | transferableOutput, 12 | transferableOutputBytes, 13 | ); 14 | -------------------------------------------------------------------------------- /src/serializable/avax/typeGuards.ts: -------------------------------------------------------------------------------- 1 | import type { BaseTx } from './baseTx'; 2 | import type { Serializable } from '../common/types'; 3 | import type { TransferableOutput } from './transferableOutput'; 4 | import type { TransferableInput } from './transferableInput'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | export function isBaseTx(tx: Serializable): tx is BaseTx { 8 | return tx._type === TypeSymbols.BaseTx; 9 | } 10 | 11 | export function isTransferableOutput( 12 | out: Serializable, 13 | ): out is TransferableOutput { 14 | return out._type === TypeSymbols.TransferableOutput; 15 | } 16 | 17 | export function isTransferableInput( 18 | out: Serializable, 19 | ): out is TransferableInput { 20 | return out._type === TypeSymbols.TransferableInput; 21 | } 22 | -------------------------------------------------------------------------------- /src/serializable/avax/utxo.test.ts: -------------------------------------------------------------------------------- 1 | import { hexToBuffer } from '../../utils/buffer'; 2 | import { describe, it } from 'vitest'; 3 | 4 | import { getPVMManager } from '../pvm/codec'; 5 | import { Utxo } from './utxo'; 6 | 7 | // testSerialization('Utxo', Utxo, utxo, utxoBytes); 8 | 9 | describe('examples with real data', () => { 10 | it('samlpe 1', () => { 11 | const bytes = 12 | '0x00004cc3cf2e380c03f0227193a6573d1940350a43f028123ca68c14f54e33a11dcf000000093d9bdac0ed1d761330cf680efdeb1a42159eb387d6d2950c96f7d28f61bbe2aa000000160000000063af7b800000000700000002540be40000000000000000000000000100000001e0cfe8cae22827d032805ded484e393ce51cbedb7f24bb9c'; 13 | 14 | getPVMManager().unpack(hexToBuffer(bytes), Utxo); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/serializable/avax/utxoId.test.ts: -------------------------------------------------------------------------------- 1 | import { UTXOID } from './utxoId'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { utxoId, utxoIdBytes } from '../../fixtures/avax'; 5 | import { testSerialization } from '../../fixtures/utils/serializable'; 6 | import { Id } from '../fxs/common'; 7 | import { Int } from '../primitives'; 8 | 9 | testSerialization('UTXOID', UTXOID, utxoId, utxoIdBytes); 10 | 11 | describe('UTXOID', () => { 12 | it('generates correct id', () => { 13 | expect( 14 | new UTXOID( 15 | Id.fromHex( 16 | '0400000000000000000000000000000000000000000000000000000000000000', 17 | ), 18 | new Int(0), 19 | ).ID(), 20 | ).toEqual('HBD3fiEVpzFy569fjPgGKa7GZayj1K1tSWcRs2QWE3ZfjNzWv'); 21 | 22 | expect( 23 | new UTXOID( 24 | Id.fromHex( 25 | '0x0500000000000000000000000000000000000000000000000000000000000000', 26 | ), 27 | new Int(4), 28 | ).ID(), 29 | ).toEqual('ZsXUqJ1rFapUSA7mRGJqGMFPjWtx1YHdb4csN6GWL9zZZ7sGs'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/serializable/avm/abstractTx.ts: -------------------------------------------------------------------------------- 1 | import { AvaxTx } from '../avax/avaxTx'; 2 | import { AVM } from '../constants'; 3 | 4 | export abstract class AVMTx extends AvaxTx { 5 | vm = AVM; 6 | } 7 | -------------------------------------------------------------------------------- /src/serializable/avm/baseTx.test.ts: -------------------------------------------------------------------------------- 1 | import { avmBaseTx, avmBaseTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { BaseTx } from './baseTx'; 4 | 5 | testSerialization('BaseTx', BaseTx, avmBaseTx, avmBaseTxBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/avm/baseTx.ts: -------------------------------------------------------------------------------- 1 | import { unpack } from '../../utils/struct'; 2 | import { BaseTx as AvaxBaseTx } from '../avax/baseTx'; 3 | import type { Codec } from '../codec/codec'; 4 | import { serializable } from '../common/types'; 5 | import { AVMTx } from './abstractTx'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see https://docs.avax.network/specs/avm-transaction-serialization#unsigned-basetx 10 | */ 11 | @serializable() 12 | export class BaseTx extends AVMTx { 13 | _type = TypeSymbols.AvmBaseTx; 14 | 15 | constructor(public readonly baseTx: AvaxBaseTx) { 16 | super(); 17 | } 18 | 19 | static fromBytes(bytes: Uint8Array, codec: Codec): [BaseTx, Uint8Array] { 20 | const [baseTx, remaining] = unpack(bytes, [AvaxBaseTx], codec); 21 | return [new BaseTx(baseTx), remaining]; 22 | } 23 | 24 | toBytes(codec: Codec) { 25 | return this.baseTx.toBytes(codec); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/serializable/avm/codec.ts: -------------------------------------------------------------------------------- 1 | import { BaseTx } from './baseTx'; 2 | import { Codec } from '../codec/codec'; 3 | import { Manager } from '../codec/manager'; 4 | import * as NftFx from '../fxs/nft'; 5 | import * as Secp256k1Fx from '../fxs/secp256k1'; 6 | import { CreateAssetTx } from './createAssetTx'; 7 | import { ExportTx } from './exportTx'; 8 | import { ImportTx } from './importTx'; 9 | import { OperationTx } from './operationTx'; 10 | 11 | // https://github.com/ava-labs/avalanchego/blob/master/vms/avm/txs/parser.go 12 | // https://github.com/ava-labs/avalanchego/blob/master/wallet/chain/x/constants.go 13 | let manager: Manager; 14 | 15 | export const getAVMManager = () => { 16 | if (manager) return manager; 17 | 18 | manager = new Manager(); 19 | manager.RegisterCodec( 20 | 0, 21 | new Codec([ 22 | BaseTx, 23 | CreateAssetTx, 24 | OperationTx, 25 | ImportTx, 26 | ExportTx, 27 | ...Secp256k1Fx.TypeRegistry, 28 | ...NftFx.TypeRegistry, 29 | // TODO: ...PropertyFx.TypeRegistry, 30 | ]), 31 | ); 32 | return manager; 33 | }; 34 | -------------------------------------------------------------------------------- /src/serializable/avm/createAssetTx.test.ts: -------------------------------------------------------------------------------- 1 | import { createAssetTx, createAssetTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { CreateAssetTx } from './createAssetTx'; 4 | 5 | testSerialization( 6 | 'CreateAssetTx', 7 | CreateAssetTx, 8 | createAssetTx, 9 | createAssetTxBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/avm/exportTx.test.ts: -------------------------------------------------------------------------------- 1 | import { exportTx, exportTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { ExportTx } from './exportTx'; 4 | 5 | testSerialization('ExportTx', ExportTx, exportTx, exportTxBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/avm/exportTx.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import { packList, toListStruct } from '../../utils/serializeList'; 3 | import { pack, unpack } from '../../utils/struct'; 4 | import { BaseTx } from '../avax/baseTx'; 5 | import { TransferableOutput } from '../avax/transferableOutput'; 6 | import type { Codec } from '../codec/codec'; 7 | import { serializable } from '../common/types'; 8 | import { Id } from '../fxs/common'; 9 | import { AVMTx } from './abstractTx'; 10 | import { TypeSymbols } from '../constants'; 11 | 12 | /** 13 | * @see https://docs.avax.network/specs/avm-transaction-serialization#unsigned-Exporttx 14 | */ 15 | @serializable() 16 | export class ExportTx extends AVMTx { 17 | _type = TypeSymbols.AvmExportTx; 18 | constructor( 19 | public readonly baseTx: BaseTx, 20 | public readonly destination: Id, 21 | public readonly outs: TransferableOutput[], 22 | ) { 23 | super(); 24 | } 25 | 26 | static fromBytes(bytes: Uint8Array, codec: Codec): [ExportTx, Uint8Array] { 27 | const [baseTx, sourceChain, outs, remaining] = unpack( 28 | bytes, 29 | [BaseTx, Id, toListStruct(TransferableOutput)], 30 | codec, 31 | ); 32 | return [new ExportTx(baseTx, sourceChain, outs), remaining]; 33 | } 34 | 35 | toBytes(codec: Codec) { 36 | return concatBytes( 37 | pack([this.baseTx, this.destination], codec), 38 | packList(this.outs, codec), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/serializable/avm/importTx.test.ts: -------------------------------------------------------------------------------- 1 | import { importTx, importTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { ImportTx } from './importTx'; 4 | 5 | testSerialization('ImportTx', ImportTx, importTx, importTxBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/avm/importTx.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import { packList, toListStruct } from '../../utils/serializeList'; 3 | import { pack, unpack } from '../../utils/struct'; 4 | import { BaseTx } from '../avax/baseTx'; 5 | import { TransferableInput } from '../avax/transferableInput'; 6 | import type { Codec } from '../codec/codec'; 7 | import { serializable } from '../common/types'; 8 | import { Id } from '../fxs/common'; 9 | import { AVMTx } from './abstractTx'; 10 | import { TypeSymbols } from '../constants'; 11 | 12 | /** 13 | * @see https://docs.avax.network/specs/avm-transaction-serialization#unsigned-importtx 14 | */ 15 | @serializable() 16 | export class ImportTx extends AVMTx { 17 | _type = TypeSymbols.AvmImportTx; 18 | 19 | constructor( 20 | readonly baseTx: BaseTx, 21 | readonly sourceChain: Id, 22 | readonly ins: TransferableInput[], 23 | ) { 24 | super(); 25 | } 26 | 27 | getSigIndices() { 28 | return this.ins 29 | .map((inp) => inp.sigIndicies()) 30 | .concat(super.getSigIndices()); 31 | } 32 | 33 | static fromBytes(bytes: Uint8Array, codec: Codec): [ImportTx, Uint8Array] { 34 | const [baseTx, sourceChain, ins, remaining] = unpack( 35 | bytes, 36 | [BaseTx, Id, toListStruct(TransferableInput)], 37 | codec, 38 | ); 39 | return [new ImportTx(baseTx, sourceChain, ins), remaining]; 40 | } 41 | 42 | toBytes(codec: Codec) { 43 | return concatBytes( 44 | pack([this.baseTx, this.sourceChain], codec), 45 | packList(this.ins, codec), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/serializable/avm/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseTx } from './baseTx'; 2 | import { CreateAssetTx } from './createAssetTx'; 3 | import { ExportTx } from './exportTx'; 4 | import { ImportTx } from './importTx'; 5 | import { InitialState } from './initialState'; 6 | import { OperationTx } from './operationTx'; 7 | export { getAVMManager } from './codec'; 8 | export * from './typeGuards'; 9 | export { BaseTx, CreateAssetTx, ExportTx, ImportTx, InitialState, OperationTx }; 10 | -------------------------------------------------------------------------------- /src/serializable/avm/initialState.test.ts: -------------------------------------------------------------------------------- 1 | import { initialState, initialStateBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { InitialState } from './initialState'; 4 | 5 | testSerialization( 6 | 'InitialState', 7 | InitialState, 8 | initialState, 9 | initialStateBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/avm/initialState.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import type { Codec } from '../codec/codec'; 3 | import type { Serializable } from '../common/types'; 4 | import { serializable } from '../common/types'; 5 | import { Int } from '../primitives'; 6 | import { unpackCodecList } from '../../utils/serializeList'; 7 | import { unpack } from '../../utils/struct'; 8 | import { TypeSymbols } from '../constants'; 9 | 10 | /** 11 | * @see https://docs.avax.network/specs/avm-transaction-serialization#initial-state 12 | */ 13 | @serializable() 14 | export class InitialState { 15 | _type = TypeSymbols.InitialState; 16 | 17 | constructor( 18 | private readonly fxId: Int, 19 | private readonly outputs: Serializable[], 20 | ) {} 21 | 22 | static fromBytes( 23 | bytes: Uint8Array, 24 | codec: Codec, 25 | ): [InitialState, Uint8Array] { 26 | const [fxId, outputs, rest] = unpack(bytes, [Int, unpackCodecList], codec); 27 | return [new InitialState(fxId, outputs), rest]; 28 | } 29 | 30 | toBytes(codec: Codec) { 31 | return concatBytes(this.fxId.toBytes(), codec.PackPrefixList(this.outputs)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/serializable/avm/operationTx.test.ts: -------------------------------------------------------------------------------- 1 | import { operationTx, operationTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { OperationTx } from './operationTx'; 4 | 5 | testSerialization('OperationTx', OperationTx, operationTx, operationTxBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/avm/operationTx.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../utils/buffer'; 2 | import { packList, toListStruct } from '../../utils/serializeList'; 3 | import { unpack } from '../../utils/struct'; 4 | import { BaseTx } from '../avax/baseTx'; 5 | import { TransferableOp } from '../avax/transferableOp'; 6 | import type { Codec } from '../codec/codec'; 7 | import { serializable } from '../common/types'; 8 | import { TypeSymbols } from '../constants'; 9 | 10 | /** 11 | * @see https://docs.avax.network/specs/avm-transaction-serialization#unsigned-OperationTx 12 | */ 13 | @serializable() 14 | export class OperationTx { 15 | _type = TypeSymbols.OperationTx; 16 | 17 | constructor( 18 | private readonly baseTx: BaseTx, 19 | private readonly ops: TransferableOp[], 20 | ) {} 21 | 22 | static fromBytes(bytes: Uint8Array, codec: Codec): [OperationTx, Uint8Array] { 23 | const [baseTx, ops, remaining] = unpack( 24 | bytes, 25 | [BaseTx, toListStruct(TransferableOp)], 26 | codec, 27 | ); 28 | return [new OperationTx(baseTx, ops), remaining]; 29 | } 30 | 31 | toBytes(codec: Codec) { 32 | return concatBytes(this.baseTx.toBytes(codec), packList(this.ops, codec)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/serializable/avm/typeGuards.ts: -------------------------------------------------------------------------------- 1 | import type { Serializable } from '../common/types'; 2 | import type { BaseTx } from './baseTx'; 3 | import type { CreateAssetTx } from './createAssetTx'; 4 | import type { ExportTx } from './exportTx'; 5 | import type { ImportTx } from './importTx'; 6 | import type { OperationTx } from './operationTx'; 7 | import { TypeSymbols } from '../constants'; 8 | 9 | export function isAvmBaseTx(tx: Serializable): tx is BaseTx { 10 | return tx._type === TypeSymbols.AvmBaseTx; 11 | } 12 | 13 | export function isExportTx(tx: Serializable): tx is ExportTx { 14 | return tx._type === TypeSymbols.AvmExportTx; 15 | } 16 | 17 | export function isImportTx(tx: Serializable): tx is ImportTx { 18 | return tx._type === TypeSymbols.AvmImportTx; 19 | } 20 | 21 | export function isCreateAssetTx(tx: Serializable): tx is CreateAssetTx { 22 | return tx._type === TypeSymbols.CreateAssetTx; 23 | } 24 | 25 | export function isOperationTx(tx: Serializable): tx is OperationTx { 26 | return tx._type === TypeSymbols.OperationTx; 27 | } 28 | -------------------------------------------------------------------------------- /src/serializable/codec/index.ts: -------------------------------------------------------------------------------- 1 | export { Codec } from './codec'; 2 | export { Manager } from './manager'; 3 | -------------------------------------------------------------------------------- /src/serializable/common/types.ts: -------------------------------------------------------------------------------- 1 | import type { Codec } from '../../serializable/codec'; 2 | import type { TypeSymbols } from '../constants'; 3 | 4 | export interface Serializable { 5 | _type: TypeSymbols; 6 | 7 | toBytes(codec: Codec): Uint8Array; 8 | } 9 | 10 | export interface SerializableStatic { 11 | new (...args: any[]): Serializable; 12 | 13 | fromBytes(bytes: Uint8Array, codec: Codec): [Serializable, Uint8Array]; 14 | } 15 | 16 | export function staticImplements() { 17 | return (constructor: U) => { 18 | constructor; 19 | }; 20 | } 21 | 22 | export function serializable() { 23 | return staticImplements(); 24 | } 25 | 26 | export interface Amounter extends Serializable { 27 | amount(): bigint; 28 | } 29 | -------------------------------------------------------------------------------- /src/serializable/evm/abstractTx.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '../../vms/common/transaction'; 2 | import { EVM } from '../constants'; 3 | import type { Id } from '../fxs/common'; 4 | 5 | export abstract class EVMTx extends Transaction { 6 | abstract blockchainId: Id; 7 | vm = EVM; 8 | 9 | getBlockchainId = () => { 10 | return this.blockchainId.toString(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/serializable/evm/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec, Manager } from '../codec'; 2 | import * as Secp256k1Fx from '../fxs/secp256k1'; 3 | import { ExportTx } from './exportTx'; 4 | import { ImportTx } from './importTx'; 5 | 6 | // https://github.com/ava-labs/coreth/blob/master/plugin/evm/codec.go 7 | let manager; 8 | export const codec = new Codec([ 9 | ImportTx, // 0 10 | ExportTx, // 1 11 | 12 | ...Array(3).fill(undefined), 13 | 14 | ...Secp256k1Fx.TypeRegistry, 15 | Secp256k1Fx.Input, 16 | Secp256k1Fx.OutputOwners, 17 | ]); 18 | 19 | export const getEVMManager = () => { 20 | if (manager) return manager; 21 | manager = new Manager(); 22 | manager.RegisterCodec(0, codec); 23 | return manager; 24 | }; 25 | -------------------------------------------------------------------------------- /src/serializable/evm/exportTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testEVMCodec } from '../../fixtures/codec'; 2 | import { exportTx, exportTxBytes } from '../../fixtures/evm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { ExportTx } from './exportTx'; 5 | 6 | testSerialization('ExportTx', ExportTx, exportTx, exportTxBytes, testEVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/evm/importTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testEVMCodec } from '../../fixtures/codec'; 2 | import { importTx, importTxBytes } from '../../fixtures/evm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { ImportTx } from './importTx'; 5 | 6 | testSerialization('ImportTx', ImportTx, importTx, importTxBytes, testEVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/evm/index.ts: -------------------------------------------------------------------------------- 1 | export { EVMTx } from './abstractTx'; 2 | export { ExportTx } from './exportTx'; 3 | export { ImportTx } from './importTx'; 4 | export { Input } from './input'; 5 | export { Output } from './output'; 6 | export * from './typeGuards'; 7 | -------------------------------------------------------------------------------- /src/serializable/evm/input.test.ts: -------------------------------------------------------------------------------- 1 | import { testEVMCodec } from '../../fixtures/codec'; 2 | import { input, inputBytes } from '../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { Input } from '../fxs/secp256k1'; 5 | 6 | testSerialization('Input', Input, input, inputBytes, testEVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/evm/input.ts: -------------------------------------------------------------------------------- 1 | import { packSwitched, unpack } from '../../utils/struct'; 2 | import { serializable } from '../../vms/common/types'; 3 | import type { Codec } from '../codec'; 4 | import { Address, Id } from '../fxs/common'; 5 | import { BigIntPr } from '../primitives'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see 10 | */ 11 | @serializable() 12 | export class Input { 13 | _type = TypeSymbols.EvmInput; 14 | 15 | constructor( 16 | public readonly address: Address, 17 | public readonly amount: BigIntPr, 18 | public readonly assetId: Id, 19 | public readonly nonce: BigIntPr, 20 | ) {} 21 | 22 | static fromBytes(bytes: Uint8Array, codec: Codec): [Input, Uint8Array] { 23 | const [address, amount, assetId, nonce, rest] = unpack( 24 | bytes, 25 | [Address, BigIntPr, Id, BigIntPr], 26 | codec, 27 | ); 28 | return [new Input(address, amount, assetId, nonce), rest]; 29 | } 30 | 31 | static compare = (a: Input, b: Input) => { 32 | if (a.address.value() !== b.address.value()) { 33 | return a.address.value().localeCompare(b.address.value()); 34 | } 35 | return a.assetId.value().localeCompare(b.assetId.value()); 36 | }; 37 | 38 | toBytes(codec: Codec) { 39 | return packSwitched( 40 | codec, 41 | this.address, 42 | this.amount, 43 | this.assetId, 44 | this.nonce, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/serializable/evm/output.test.ts: -------------------------------------------------------------------------------- 1 | import { testEVMCodec } from '../../fixtures/codec'; 2 | import { output, outputBytes } from '../../fixtures/evm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { Output } from './output'; 5 | 6 | testSerialization('Output', Output, output, outputBytes, testEVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/evm/output.ts: -------------------------------------------------------------------------------- 1 | import { packSwitched, unpack } from '../../utils/struct'; 2 | import { serializable } from '../../vms/common/types'; 3 | import type { Codec } from '../codec'; 4 | import { Address, Id } from '../fxs/common'; 5 | import { BigIntPr } from '../primitives'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see 10 | */ 11 | @serializable() 12 | export class Output { 13 | _type = TypeSymbols.EvmOutput; 14 | 15 | constructor( 16 | public readonly address: Address, 17 | public readonly amount: BigIntPr, 18 | public readonly assetId: Id, 19 | ) {} 20 | 21 | static fromBytes(bytes: Uint8Array, codec: Codec): [Output, Uint8Array] { 22 | const [address, amount, assetId, rest] = unpack( 23 | bytes, 24 | [Address, BigIntPr, Id], 25 | codec, 26 | ); 27 | return [new Output(address, amount, assetId), rest]; 28 | } 29 | 30 | toBytes(codec: Codec) { 31 | return packSwitched(codec, this.address, this.amount, this.assetId); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/serializable/evm/typeGuards.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isImportTx, 3 | isExportTx, 4 | isImportExportTx, 5 | isEvmTx, 6 | } from './typeGuards'; 7 | import { describe, it, expect } from 'vitest'; 8 | 9 | import { TypeSymbols } from '../constants'; 10 | import type { Transaction } from '../../vms/common'; 11 | import { onlyChecksOneGuard } from '../../fixtures/utils/typeguards'; 12 | 13 | const guards = [isImportTx, isExportTx]; 14 | 15 | describe('evm/typeGuards', function () { 16 | it('isImportTx', () => { 17 | const tx = { 18 | _type: TypeSymbols.EvmImportTx, 19 | } as Transaction; 20 | expect(isImportTx(tx)).toBe(true); 21 | expect(isImportExportTx(tx)).toBe(true); 22 | expect(isEvmTx(tx)).toBe(true); 23 | expect(onlyChecksOneGuard(tx, guards)).toBe(true); 24 | }); 25 | 26 | it('isExportTx', () => { 27 | const tx = { 28 | _type: TypeSymbols.EvmExportTx, 29 | } as Transaction; 30 | expect(isExportTx(tx)).toBe(true); 31 | expect(isImportExportTx(tx)).toBe(true); 32 | expect(isEvmTx(tx)).toBe(true); 33 | expect(onlyChecksOneGuard(tx, guards)).toBe(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/serializable/evm/typeGuards.ts: -------------------------------------------------------------------------------- 1 | import type { Transaction } from '../../vms/common/transaction'; 2 | import type { EVMTx } from './abstractTx'; 3 | import type { ExportTx } from './exportTx'; 4 | import type { ImportTx } from './importTx'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | export function isExportTx(tx: Transaction): tx is ExportTx { 8 | return tx._type == TypeSymbols.EvmExportTx; 9 | } 10 | 11 | export function isImportTx(tx: Transaction): tx is ImportTx { 12 | return tx._type == TypeSymbols.EvmImportTx; 13 | } 14 | 15 | export function isEvmTx(tx: Transaction): tx is EVMTx { 16 | return isImportTx(tx) || isExportTx(tx); 17 | } 18 | 19 | export function isImportExportTx(tx: Transaction): tx is ImportTx | ExportTx { 20 | if (!(isExportTx(tx) || isImportTx(tx))) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/address.test.ts: -------------------------------------------------------------------------------- 1 | import { address, addressBytes } from '../../../fixtures/common'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { Address } from './address'; 4 | 5 | testSerialization('Address', Address, address, addressBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/address.ts: -------------------------------------------------------------------------------- 1 | import { customInspectSymbol } from '../../../constants/node'; 2 | import { bech32ToBytes, formatBech32 } from '../../../utils/address'; 3 | import { bufferToHex, hexToBuffer, padLeft } from '../../../utils/buffer'; 4 | import { serializable } from '../../common/types'; 5 | import { Primitives } from '../../primitives/primatives'; 6 | import { TypeSymbols } from '../../constants'; 7 | 8 | @serializable() 9 | export class Address extends Primitives { 10 | _type = TypeSymbols.Address; 11 | constructor(private readonly address: Uint8Array) { 12 | super(); 13 | } 14 | 15 | static fromBytes(buf: Uint8Array): [Address, Uint8Array] { 16 | return [new Address(buf.slice(0, 20)), buf.slice(20)]; 17 | } 18 | 19 | [customInspectSymbol](_, options: any) { 20 | return options.stylize(this.toJSON(), 'string'); 21 | } 22 | 23 | toJSON(hrp = 'avax') { 24 | return this.toString(hrp); 25 | } 26 | 27 | //decodes from bech32 Addresses 28 | static fromString(addr: string): Address { 29 | return new Address(bech32ToBytes(addr)); 30 | } 31 | 32 | static fromHex(hex: string): Address { 33 | return new Address(hexToBuffer(hex)); 34 | } 35 | 36 | toHex(): string { 37 | return bufferToHex(this.address); 38 | } 39 | 40 | toBytes() { 41 | return padLeft(this.address, 20); 42 | } 43 | 44 | toString(hrp = 'avax') { 45 | return formatBech32(hrp, this.address); 46 | } 47 | 48 | value() { 49 | return this.toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/blsPublicKey.test.ts: -------------------------------------------------------------------------------- 1 | import { blsPublicKey, blsPublicKeyBytes } from '../../../fixtures/primitives'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { BlsPublicKey } from './blsPublicKey'; 4 | 5 | testSerialization( 6 | 'BlsPublicKey', 7 | BlsPublicKey, 8 | blsPublicKey, 9 | blsPublicKeyBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/blsPublicKey.ts: -------------------------------------------------------------------------------- 1 | import type { PublicKey } from '../../../crypto/bls'; 2 | import { 3 | PUBLIC_KEY_LENGTH, 4 | publicKeyFromBytes, 5 | publicKeyToBytes, 6 | } from '../../../crypto/bls'; 7 | import { hexToBuffer } from '../../../utils/buffer'; 8 | import { serializable } from '../../common/types'; 9 | import { TypeSymbols } from '../../constants'; 10 | import { Primitives } from '../../primitives/primatives'; 11 | 12 | @serializable() 13 | export class BlsPublicKey extends Primitives { 14 | _type = TypeSymbols.BlsPublicKey; 15 | 16 | constructor(public readonly publicKey: PublicKey) { 17 | super(); 18 | } 19 | 20 | static fromPublicKeyBytes(publicKeyBytes: Uint8Array): BlsPublicKey { 21 | return new BlsPublicKey(publicKeyFromBytes(publicKeyBytes)); 22 | } 23 | 24 | static fromBytes( 25 | bytes: Uint8Array, 26 | ): [blsPublicKey: BlsPublicKey, rest: Uint8Array] { 27 | const blsPublicKeyBytes = bytes.slice(0, PUBLIC_KEY_LENGTH); 28 | const publicKey = publicKeyFromBytes(blsPublicKeyBytes); 29 | const rest = bytes.slice(PUBLIC_KEY_LENGTH); 30 | 31 | return [new BlsPublicKey(publicKey), rest]; 32 | } 33 | 34 | static fromHex(hex: string): BlsPublicKey { 35 | return new BlsPublicKey(publicKeyFromBytes(hexToBuffer(hex))); 36 | } 37 | 38 | toBytes() { 39 | return publicKeyToBytes(this.publicKey); 40 | } 41 | 42 | toString() { 43 | return this.publicKey.toHex(); 44 | } 45 | 46 | toJSON() { 47 | return this.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/blsSignature.test.ts: -------------------------------------------------------------------------------- 1 | import { blsSignature, blsSignatureBytes } from '../../../fixtures/primitives'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { BlsSignature } from './blsSignature'; 4 | 5 | testSerialization( 6 | 'BlsSignature', 7 | BlsSignature, 8 | blsSignature, 9 | blsSignatureBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/blsSignature.ts: -------------------------------------------------------------------------------- 1 | import type { Signature } from '../../../crypto/bls'; 2 | import { 3 | SIGNATURE_LENGTH, 4 | signatureFromBytes, 5 | signatureToBytes, 6 | } from '../../../crypto/bls'; 7 | import { hexToBuffer } from '../../../utils/buffer'; 8 | import { serializable } from '../../common/types'; 9 | import { TypeSymbols } from '../../constants'; 10 | import { Primitives } from '../../primitives/primatives'; 11 | 12 | @serializable() 13 | export class BlsSignature extends Primitives { 14 | _type = TypeSymbols.BlsSignature; 15 | 16 | constructor(public readonly signature: Signature) { 17 | super(); 18 | } 19 | 20 | static fromSignatureBytes(signatureBytes: Uint8Array): BlsSignature { 21 | return new BlsSignature(signatureFromBytes(signatureBytes)); 22 | } 23 | 24 | static fromBytes( 25 | bytes: Uint8Array, 26 | ): [blsSignature: BlsSignature, rest: Uint8Array] { 27 | const blsSignatureBytes = bytes.slice(0, SIGNATURE_LENGTH); 28 | const signature = signatureFromBytes(blsSignatureBytes); 29 | const rest = bytes.slice(SIGNATURE_LENGTH); 30 | 31 | return [new BlsSignature(signature), rest]; 32 | } 33 | 34 | static fromHex(hex: string): BlsSignature { 35 | return new BlsSignature(signatureFromBytes(hexToBuffer(hex))); 36 | } 37 | 38 | toBytes() { 39 | return signatureToBytes(this.signature); 40 | } 41 | 42 | toString() { 43 | return this.signature.toHex(); 44 | } 45 | 46 | toJSON() { 47 | return this.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/id.test.ts: -------------------------------------------------------------------------------- 1 | import { id, idBytes } from '../../../fixtures/common'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { testSerialization } from '../../../fixtures/utils/serializable'; 5 | import { Id } from './id'; 6 | 7 | testSerialization('Id', Id, id, idBytes); 8 | 9 | describe('id', function () { 10 | it('works correctly', () => { 11 | const id = Id.fromHex( 12 | '0x6176617800000000000000000000000000000000000000000000000000000000', 13 | ); 14 | const expectedIDStr = 'jvYiYUgxB6hG1dBobJK2JSoyuLtmFiDoXmUVxmAB7juqfbVtu'; 15 | 16 | const idStr = id.toString(); 17 | 18 | expect(idStr).toEqual(expectedIDStr); 19 | 20 | expect(Id.fromString(expectedIDStr).toBytes()).toEqual( 21 | new Uint8Array([ 22 | 0x61, 0x76, 0x61, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | ]), 26 | ); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/id.ts: -------------------------------------------------------------------------------- 1 | import { customInspectSymbol } from '../../../constants/node'; 2 | import { base58check } from '../../../utils/base58'; 3 | import { hexToBuffer, padLeft } from '../../../utils/buffer'; 4 | import { bytesCompare } from '../../../utils/bytesCompare'; 5 | import { serializable } from '../../common/types'; 6 | import { Primitives } from '../../primitives/primatives'; 7 | import { TypeSymbols } from '../../constants'; 8 | 9 | /** 10 | * Number of bytes per ID. 11 | */ 12 | export const ID_LEN = 32; 13 | 14 | @serializable() 15 | export class Id extends Primitives { 16 | _type = TypeSymbols.Id; 17 | constructor(private readonly idVal: Uint8Array) { 18 | super(); 19 | } 20 | 21 | static fromBytes(buf: Uint8Array): [Id, Uint8Array] { 22 | return [new Id(buf.slice(0, ID_LEN)), buf.slice(ID_LEN)]; 23 | } 24 | 25 | static compare(id1: Id, id2: Id): number { 26 | return bytesCompare(id1.toBytes(), id2.toBytes()); 27 | } 28 | 29 | [customInspectSymbol](_, options: any) { 30 | return options.stylize(this.toString(), 'string'); 31 | } 32 | 33 | toBytes() { 34 | return padLeft(this.idVal, ID_LEN); 35 | } 36 | 37 | toJSON() { 38 | return this.toString(); 39 | } 40 | 41 | toString() { 42 | return base58check.encode(this.toBytes()); 43 | } 44 | 45 | static fromString(str: string) { 46 | return Id.fromBytes(base58check.decode(str))[0]; 47 | } 48 | 49 | static fromHex(hex: string): Id { 50 | return new Id(hexToBuffer(hex)); 51 | } 52 | 53 | value() { 54 | return this.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/index.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './address'; 2 | import { BlsSignature } from './blsSignature'; 3 | import { BlsPublicKey } from './blsPublicKey'; 4 | import { Id } from './id'; 5 | import { NodeId } from './nodeId'; 6 | 7 | export { Address, BlsSignature, BlsPublicKey, Id, NodeId }; 8 | -------------------------------------------------------------------------------- /src/serializable/fxs/common/nodeId.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../../fixtures/codec'; 2 | import { nodeId, nodeIdBytes } from '../../../fixtures/common'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | import { NodeId } from './nodeId'; 5 | 6 | testSerialization('NodeId', NodeId, nodeId, nodeIdBytes, testPVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/credential.ts: -------------------------------------------------------------------------------- 1 | import { Credential as SepkCredential } from '../secp256k1/credential'; 2 | import { TypeSymbols } from '../../constants'; 3 | 4 | export class Credential extends SepkCredential { 5 | _type = TypeSymbols.NftFxCredential; 6 | } 7 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/index.ts: -------------------------------------------------------------------------------- 1 | import { Credential } from './credential'; 2 | import { MintOperation } from './mintOperation'; 3 | import { MintOutput } from './mintOutput'; 4 | import { TransferOperation } from './transferOperation'; 5 | import { TransferOutput } from './transferOutput'; 6 | 7 | // https://github.com/ava-labs/avalanchego/blob/master/vms/nftfx/fx.go 8 | const TypeRegistry = Object.freeze([ 9 | MintOutput, 10 | TransferOutput, 11 | MintOperation, 12 | TransferOperation, 13 | Credential, 14 | ]); 15 | 16 | export { 17 | Credential, 18 | MintOutput, 19 | TransferOutput, 20 | MintOperation, 21 | TransferOperation, 22 | TypeRegistry, 23 | }; 24 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/mintOperation.test.ts: -------------------------------------------------------------------------------- 1 | import { mintOperation, mintOperationBytes } from '../../../fixtures/nft'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { MintOperation } from './mintOperation'; 4 | 5 | testSerialization( 6 | 'MintOperation', 7 | MintOperation, 8 | mintOperation, 9 | mintOperationBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/mintOperation.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { Bytes, Int } from '../../primitives'; 3 | import { pack, unpack } from '../../../utils/struct'; 4 | import { Input, OutputOwnersList } from '../secp256k1'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/nftfx/mint_operation.go 9 | * @see https://docs.avax.network/specs/avm-transaction-serialization#nft-mint-op 10 | */ 11 | @serializable() 12 | export class MintOperation { 13 | _type = TypeSymbols.NftFxMintOperation; 14 | constructor( 15 | private readonly input: Input, 16 | private readonly groupId: Int, 17 | private readonly payload: Bytes, 18 | private readonly outputOwnerList: OutputOwnersList, 19 | ) {} 20 | 21 | static fromBytes(bytes: Uint8Array): [MintOperation, Uint8Array] { 22 | const [input, groupId, payload, outputOwnerList, remaining] = unpack( 23 | bytes, 24 | [Input, Int, Bytes, OutputOwnersList] as const, 25 | ); 26 | return [ 27 | new MintOperation(input, groupId, payload, outputOwnerList), 28 | remaining, 29 | ]; 30 | } 31 | 32 | toBytes(codec) { 33 | return pack( 34 | [this.input, this.groupId, this.payload, this.outputOwnerList], 35 | codec, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/mintOutput.test.ts: -------------------------------------------------------------------------------- 1 | import { MintOutput } from '.'; 2 | import { mintOutput, mintOutputBytes } from '../../../fixtures/nft'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization('MintOutput', MintOutput, mintOutput, mintOutputBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/mintOutput.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { Int } from '../../primitives'; 3 | import { pack, unpack } from '../../../utils/struct'; 4 | import { OutputOwners } from '../secp256k1'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/nftfx/mint_output.go 9 | * @see https://docs.avax.network/specs/avm-transaction-serialization/#nft-mint-output 10 | */ 11 | @serializable() 12 | export class MintOutput { 13 | _type = TypeSymbols.NftFxMintOutput; 14 | 15 | constructor( 16 | private readonly groupId: Int, 17 | private readonly outputOwners: OutputOwners, 18 | ) {} 19 | 20 | static fromBytes(bytes: Uint8Array, codec): [MintOutput, Uint8Array] { 21 | const [groupId, owners, remaining] = unpack( 22 | bytes, 23 | [Int, OutputOwners] as const, 24 | codec, 25 | ); 26 | 27 | return [new MintOutput(groupId, owners), remaining]; 28 | } 29 | 30 | toBytes(codec) { 31 | return pack([this.groupId, this.outputOwners], codec); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/transferOperation.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | transferOperation, 3 | transferOperationBytes, 4 | } from '../../../fixtures/nft'; 5 | import { testSerialization } from '../../../fixtures/utils/serializable'; 6 | import { TransferOperation } from './transferOperation'; 7 | 8 | testSerialization( 9 | 'TransferOperation', 10 | TransferOperation, 11 | transferOperation, 12 | transferOperationBytes, 13 | ); 14 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/transferOperation.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { pack, unpack } from '../../../utils/struct'; 3 | import { Input } from '../secp256k1'; 4 | import { TransferOutput } from './transferOutput'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * https://docs.avax.network/specs/avm-transaction-serialization#nft-transfer-op 9 | * 10 | */ 11 | @serializable() 12 | export class TransferOperation { 13 | _type = TypeSymbols.NftFxTransferOperation; 14 | 15 | constructor( 16 | private readonly input: Input, 17 | private readonly output: TransferOutput, 18 | ) {} 19 | 20 | static fromBytes(bytes: Uint8Array): [TransferOperation, Uint8Array] { 21 | const [input, output, remaining] = unpack(bytes, [Input, TransferOutput]); 22 | 23 | return [new TransferOperation(input, output), remaining]; 24 | } 25 | 26 | toBytes(codec) { 27 | return pack([this.input, this.output], codec); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/transferOutput.test.ts: -------------------------------------------------------------------------------- 1 | import { TransferOutput } from '.'; 2 | import { transferOutput, transferOutputBytes } from '../../../fixtures/nft'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization( 6 | 'TransferOutput', 7 | TransferOutput, 8 | transferOutput, 9 | transferOutputBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/nft/transferOutput.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { Bytes, Int } from '../../primitives'; 3 | import { pack, unpack } from '../../../utils/struct'; 4 | import { OutputOwners } from '../secp256k1'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/nftfx/transfer_output.go 9 | * @see https://docs.avax.network/specs/avm-transaction-serialization/#nft-transfer-output 10 | */ 11 | @serializable() 12 | export class TransferOutput { 13 | _type = TypeSymbols.NftFxTransferOutput; 14 | 15 | constructor( 16 | public readonly groupId: Int, 17 | public readonly payload: Bytes, 18 | public readonly outputOwners: OutputOwners, 19 | ) {} 20 | 21 | static fromBytes(bytes: Uint8Array): [TransferOutput, Uint8Array] { 22 | const [groupId, payload, outputOwners, remaining] = unpack(bytes, [ 23 | Int, 24 | Bytes, 25 | OutputOwners, 26 | ] as const); 27 | 28 | return [new TransferOutput(groupId, payload, outputOwners), remaining]; 29 | } 30 | 31 | toBytes(codec) { 32 | return pack([this.groupId, this.payload, this.outputOwners], codec); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/serializable/fxs/pvm/L1Validator.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../../fixtures/codec'; 2 | import { l1Validator, l1ValidatorBytes } from '../../../fixtures/pvm'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | import { L1Validator } from './L1Validator'; 5 | 6 | testSerialization( 7 | 'L1Validator', 8 | L1Validator, 9 | l1Validator, 10 | l1ValidatorBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/fxs/pvm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './L1Validator'; 2 | export * from './pChainOwner'; 3 | -------------------------------------------------------------------------------- /src/serializable/fxs/pvm/pChainOwner.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../../fixtures/codec'; 2 | import { pChainOwner, pChainOwnerBytes } from '../../../fixtures/pvm'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | import { PChainOwner } from './pChainOwner'; 5 | 6 | testSerialization( 7 | 'PChainOwner', 8 | PChainOwner, 9 | pChainOwner, 10 | pChainOwnerBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/fxs/pvm/pChainOwner.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '@noble/hashes/utils'; 2 | import { toListStruct } from '../../../utils/serializeList'; 3 | import { pack, unpack } from '../../../utils/struct'; 4 | import { serializable } from '../../common/types'; 5 | import { Int } from '../../primitives'; 6 | import { Address } from '../common/address'; 7 | import { TypeSymbols } from '../../constants'; 8 | import type { Codec } from '../../codec'; 9 | 10 | /** 11 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/message/register_subnet_validator.go 12 | */ 13 | @serializable() 14 | export class PChainOwner { 15 | _type = TypeSymbols.PChainOwner; 16 | 17 | constructor( 18 | public readonly threshold: Int, 19 | public readonly addresses: Address[], 20 | ) {} 21 | 22 | getAddresses() { 23 | return this.addresses; 24 | } 25 | 26 | static fromBytes(bytes: Uint8Array, codec: Codec): [PChainOwner, Uint8Array] { 27 | const [threshold, addresses, remaining] = unpack( 28 | bytes, 29 | [Int, toListStruct(Address)], 30 | codec, 31 | ); 32 | return [new PChainOwner(threshold, addresses), remaining]; 33 | } 34 | 35 | toBytes(codec: Codec) { 36 | return concatBytes(pack([this.threshold, this.addresses], codec)); 37 | } 38 | 39 | static fromNative(addresses: readonly Uint8Array[], threshold = 1) { 40 | return new PChainOwner( 41 | new Int(threshold), 42 | addresses.map((addr) => new Address(addr)), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/credential.test.ts: -------------------------------------------------------------------------------- 1 | import { credential, credentialBytes } from '../../../fixtures/secp256k1'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { Credential } from './credential'; 4 | 5 | testSerialization('Credential', Credential, credential, credentialBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/credential.ts: -------------------------------------------------------------------------------- 1 | import { packList, unpackList } from '../../../utils/serializeList'; 2 | import type { Codec } from '../../codec'; 3 | import { serializable } from '../../common/types'; 4 | import { Signature } from './signature'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * @see https://docs.avax.network/specs/avm-transaction-serialization#secp256k1-credential 9 | */ 10 | @serializable() 11 | export class Credential { 12 | _type = TypeSymbols.Credential; 13 | 14 | constructor(private readonly signatures: Signature[]) {} 15 | 16 | static fromBytes(bytes: Uint8Array, codec: Codec): [Credential, Uint8Array] { 17 | const [sigs, remaining] = unpackList(bytes, Signature, codec); 18 | return [new Credential(sigs), remaining]; 19 | } 20 | 21 | toJSON() { 22 | return this.signatures; 23 | } 24 | 25 | static fromJSON(credentialsStrings: string[]) { 26 | return new Credential( 27 | credentialsStrings.map((str) => Signature.fromJSON(str)), 28 | ); 29 | } 30 | 31 | setSignature(index: number, signature: Uint8Array) { 32 | if (index >= this.signatures.length) { 33 | throw new Error(`index ${index} is out of bounds for credential`); 34 | } 35 | this.signatures[index] = new Signature(signature); 36 | } 37 | 38 | getSignatures() { 39 | return this.signatures.map((sig) => sig.toString()); 40 | } 41 | 42 | toBytes(codec) { 43 | return packList(this.signatures, codec); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/index.ts: -------------------------------------------------------------------------------- 1 | import { Credential } from './credential'; 2 | import { Input } from './input'; 3 | import { MintOperation } from './mintOperation'; 4 | import { MintOutput } from './mintOutput'; 5 | import { OutputOwners } from './outputOwners'; 6 | import { OutputOwnersList } from './outputOwnersList'; 7 | import { Signature } from './signature'; 8 | import { TransferInput } from './transferInput'; 9 | import { TransferOutput } from './transferOutput'; 10 | 11 | // https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/fx.go 12 | const TypeRegistry = Object.freeze([ 13 | TransferInput, 14 | MintOutput, 15 | TransferOutput, 16 | MintOperation, 17 | Credential, 18 | ]); 19 | 20 | export { 21 | Input, 22 | MintOperation, 23 | MintOutput, 24 | OutputOwners, 25 | OutputOwnersList, 26 | TransferInput, 27 | TransferOutput, 28 | Credential, 29 | Signature, 30 | TypeRegistry, 31 | }; 32 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/input.test.ts: -------------------------------------------------------------------------------- 1 | import { Input } from '.'; 2 | import { input, inputBytes } from '../../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization('Input', Input, input, inputBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/input.ts: -------------------------------------------------------------------------------- 1 | import { packList, toListStruct } from '../../../utils/serializeList'; 2 | import { unpack } from '../../../utils/struct'; 3 | import { serializable } from '../../common/types'; 4 | import { Int } from '../../primitives'; 5 | import { TypeSymbols } from '../../constants'; 6 | 7 | /** 8 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/input.go 9 | * @see https://docs.avax.network/specs/coreth-atomic-transaction-serialization/#secp256k1-transfer-input 10 | * @see https://docs.avax.network/specs/avm-transaction-serialization/#secp256k1-transfer-input 11 | * @see https://docs.avax.network/specs/platform-transaction-serialization/#secp256k1-transfer-input 12 | */ 13 | @serializable() 14 | export class Input { 15 | _type = TypeSymbols.Input; 16 | 17 | constructor(private readonly sigIndices: Int[]) {} 18 | 19 | static fromNative(sigIndicies: readonly number[]) { 20 | return new Input(sigIndicies.map((i) => new Int(i))); 21 | } 22 | 23 | static fromBytes(bytes: Uint8Array): [Input, Uint8Array] { 24 | const [sigIndices, remaining] = unpack(bytes, [toListStruct(Int)]); 25 | return [new Input(sigIndices), remaining]; 26 | } 27 | 28 | values() { 29 | return this.sigIndices.map((i) => i.value()); 30 | } 31 | 32 | toBytes(codec) { 33 | return packList(this.sigIndices, codec); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/mintOperation.test.ts: -------------------------------------------------------------------------------- 1 | import { MintOperation } from '.'; 2 | import { mintOperation, mintOperationBytes } from '../../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization( 6 | 'MintOperation', 7 | MintOperation, 8 | mintOperation, 9 | mintOperationBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/mintOperation.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../../utils/buffer'; 2 | import { unpack } from '../../../utils/struct'; 3 | import { serializable } from '../../common/types'; 4 | import { Input } from './input'; 5 | import { MintOutput } from './mintOutput'; 6 | import { TransferOutput } from './transferOutput'; 7 | import { TypeSymbols } from '../../constants'; 8 | 9 | /** 10 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/mint_operation.go 11 | * @see https://docs.avax.network/specs/avm-transaction-serialization#secp256k1-mint-operation 12 | */ 13 | @serializable() 14 | export class MintOperation { 15 | _type = TypeSymbols.SecpMintOperation; 16 | 17 | constructor( 18 | private readonly input: Input, 19 | private readonly mintOutput: MintOutput, 20 | private readonly transferOutput: TransferOutput, 21 | ) {} 22 | 23 | static fromBytes(bytes: Uint8Array): [MintOperation, Uint8Array] { 24 | const [input, mintOutput, transferOutput, remaining] = unpack(bytes, [ 25 | Input, 26 | MintOutput, 27 | TransferOutput, 28 | ] as const); 29 | 30 | return [new MintOperation(input, mintOutput, transferOutput), remaining]; 31 | } 32 | 33 | toBytes(codec) { 34 | return concatBytes( 35 | this.input.toBytes(codec), 36 | this.mintOutput.toBytes(codec), 37 | this.transferOutput.toBytes(codec), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/mintOutput.test.ts: -------------------------------------------------------------------------------- 1 | import { MintOutput } from '.'; 2 | import { mintOutput, mintOutputBytes } from '../../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization('MintOutput', MintOutput, mintOutput, mintOutputBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/mintOutput.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { OutputOwners } from './outputOwners'; 3 | import { TypeSymbols } from '../../constants'; 4 | 5 | /** 6 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/mint_output.go 7 | * @see https://docs.avax.network/specs/avm-transaction-serialization/#secp256k1-mint-output 8 | */ 9 | @serializable() 10 | export class MintOutput { 11 | _type = TypeSymbols.SecpMintOutput; 12 | 13 | constructor(private readonly outputOwners: OutputOwners) {} 14 | 15 | static fromBytes(bytes: Uint8Array, codec): [MintOutput, Uint8Array] { 16 | let owners: OutputOwners; 17 | [owners, bytes] = OutputOwners.fromBytes(bytes, codec); 18 | 19 | return [new MintOutput(owners), bytes]; 20 | } 21 | 22 | toBytes(codec) { 23 | return this.outputOwners.toBytes(codec); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/outputOwners.test.ts: -------------------------------------------------------------------------------- 1 | import { OutputOwners } from '.'; 2 | import { outputOwner, outputOwnerBytes } from '../../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization('OutputOwners', OutputOwners, outputOwner, outputOwnerBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/outputOwnersList.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | outputOwnersList, 3 | outputOwnersListBytes, 4 | } from '../../../fixtures/secp256k1'; 5 | import { testSerialization } from '../../../fixtures/utils/serializable'; 6 | import { OutputOwnersList } from './outputOwnersList'; 7 | 8 | testSerialization( 9 | 'OutputOwnersList', 10 | OutputOwnersList, 11 | outputOwnersList, 12 | outputOwnersListBytes, 13 | ); 14 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/outputOwnersList.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../../common/types'; 2 | import { packList, unpackList } from '../../../utils/serializeList'; 3 | import { OutputOwners } from './outputOwners'; 4 | import { TypeSymbols } from '../../constants'; 5 | 6 | /** 7 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/output_owners.go 8 | * @see https://docs.avax.network/specs/platform-transaction-serialization/#secp256k1-output-owners-output 9 | */ 10 | @serializable() 11 | export class OutputOwnersList { 12 | _type = TypeSymbols.OutputOwnersList; 13 | 14 | constructor(private readonly outputOwners: OutputOwners[]) {} 15 | 16 | static fromBytes(bytes: Uint8Array, codec): [OutputOwnersList, Uint8Array] { 17 | const [owners, remaining] = unpackList(bytes, OutputOwners, codec); 18 | return [new OutputOwnersList(owners), remaining]; 19 | } 20 | 21 | toBytes(codec) { 22 | return packList(this.outputOwners, codec); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/signature.test.ts: -------------------------------------------------------------------------------- 1 | import { signature, signatureBytes } from '../../../fixtures/secp256k1'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { Signature } from './signature'; 4 | 5 | testSerialization('Signature', Signature, signature, signatureBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/signature.ts: -------------------------------------------------------------------------------- 1 | import { bytesToHex } from '@noble/hashes/utils'; 2 | import { hexToBytes } from 'micro-eth-signer'; 3 | import { customInspectSymbol } from '../../../constants/node'; 4 | import { bufferToHex, padLeft } from '../../../utils/buffer'; 5 | import { serializable } from '../../common/types'; 6 | import { TypeSymbols } from '../../constants'; 7 | 8 | /** 9 | * @see https://docs.avax.network/specs/avm-transaction-serialization#secp256k1-credential 10 | */ 11 | export const SepkSignatureLength = 65; 12 | @serializable() 13 | export class Signature { 14 | _type = TypeSymbols.Signature; 15 | 16 | constructor(private readonly sig: Uint8Array) { 17 | if (sig.length !== SepkSignatureLength) { 18 | throw new Error('incorrect number of bytes for signature'); 19 | } 20 | } 21 | 22 | toJSON() { 23 | return bytesToHex(this.sig); 24 | } 25 | 26 | static fromJSON(jsonStr: string) { 27 | return new Signature(hexToBytes(jsonStr)); 28 | } 29 | 30 | static fromBytes(bytes: Uint8Array): [Signature, Uint8Array] { 31 | return [ 32 | new Signature(bytes.slice(0, SepkSignatureLength)), 33 | bytes.slice(SepkSignatureLength), 34 | ]; 35 | } 36 | 37 | [customInspectSymbol](_, options: any) { 38 | return options.stylize(this.toString(), 'string'); 39 | } 40 | 41 | toString() { 42 | return bufferToHex(this.sig); 43 | } 44 | 45 | toBytes() { 46 | return padLeft(this.sig, SepkSignatureLength); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/transferInput.test.ts: -------------------------------------------------------------------------------- 1 | import { TransferInput } from '.'; 2 | import { transferInput, transferInputBytes } from '../../../fixtures/secp256k1'; 3 | import { testSerialization } from '../../../fixtures/utils/serializable'; 4 | 5 | testSerialization( 6 | 'TransferInput', 7 | TransferInput, 8 | transferInput, 9 | transferInputBytes, 10 | ); 11 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/transferInput.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../../utils/struct'; 2 | import type { Amounter } from '../../common/types'; 3 | import { serializable } from '../../common/types'; 4 | import { BigIntPr } from '../../primitives'; 5 | import { Input } from './input'; 6 | import { TypeSymbols } from '../../constants'; 7 | 8 | /** 9 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/secp256k1fx/transfer_input.go 10 | * @see https://docs.avax.network/specs/coreth-atomic-transaction-serialization/#secp256k1-transfer-input 11 | * @see https://docs.avax.network/specs/avm-transaction-serialization/#secp256k1-transfer-input 12 | * @see https://docs.avax.network/specs/platform-transaction-serialization/#secp256k1-transfer-input 13 | */ 14 | @serializable() 15 | export class TransferInput implements Amounter { 16 | _type = TypeSymbols.TransferInput; 17 | 18 | constructor(private readonly amt: BigIntPr, private readonly input: Input) {} 19 | 20 | static fromBytes(bytes: Uint8Array): [TransferInput, Uint8Array] { 21 | const [amt, input, remaining] = unpack(bytes, [BigIntPr, Input]); 22 | return [new TransferInput(amt, input), remaining]; 23 | } 24 | 25 | static fromNative(amount: bigint, sigIndicies: number[]) { 26 | return new TransferInput( 27 | new BigIntPr(amount), 28 | Input.fromNative(sigIndicies), 29 | ); 30 | } 31 | 32 | sigIndicies() { 33 | return this.input.values(); 34 | } 35 | 36 | amount() { 37 | return this.amt.value(); 38 | } 39 | toBytes(codec) { 40 | return pack([this.amt, this.input], codec); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/serializable/fxs/secp256k1/transferOutput.test.ts: -------------------------------------------------------------------------------- 1 | import { TransferOutput } from '.'; 2 | import { 3 | transferOutput, 4 | transferOutputBytes, 5 | } from '../../../fixtures/secp256k1'; 6 | import { testSerialization } from '../../../fixtures/utils/serializable'; 7 | 8 | testSerialization( 9 | 'TransferOutput', 10 | TransferOutput, 11 | transferOutput, 12 | transferOutputBytes, 13 | ); 14 | -------------------------------------------------------------------------------- /src/serializable/index.ts: -------------------------------------------------------------------------------- 1 | export * as avaxSerial from './avax'; 2 | export { TransferableOutput } from './avax/transferableOutput'; 3 | export { TransferableInput } from './avax/transferableInput'; 4 | export * as avmSerial from './avm'; 5 | export * as evmSerial from './evm'; 6 | export * from './fxs/secp256k1'; 7 | export * from './fxs/common'; 8 | export * from './fxs/pvm'; 9 | export * as pvmSerial from './pvm'; 10 | export * from './primitives'; 11 | export * from './constants'; 12 | -------------------------------------------------------------------------------- /src/serializable/primitives/bigintpr.test.ts: -------------------------------------------------------------------------------- 1 | import { bigIntPr, bigIntPrBytes } from '../../fixtures/primitives'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { BigIntPr } from './bigintpr'; 4 | 5 | testSerialization('Bigintpr', BigIntPr, bigIntPr, bigIntPrBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/primitives/bigintpr.ts: -------------------------------------------------------------------------------- 1 | import { customInspectSymbol } from '../../constants/node'; 2 | import { bufferToBigInt, hexToBuffer, padLeft } from '../../utils/buffer'; 3 | import { serializable } from '../common/types'; 4 | import { Primitives } from './primatives'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | // typescript doesn't like BigInt as a class name 8 | @serializable() 9 | export class BigIntPr extends Primitives { 10 | _type = TypeSymbols.BigIntPr; 11 | constructor(private readonly bigint: bigint) { 12 | super(); 13 | } 14 | 15 | [customInspectSymbol]() { 16 | return this.bigint; 17 | } 18 | 19 | static fromBytes(buf: Uint8Array): [BigIntPr, Uint8Array] { 20 | return [new BigIntPr(bufferToBigInt(buf.slice(0, 8))), buf.slice(8)]; 21 | } 22 | 23 | toJSON() { 24 | return this.bigint.toString(); 25 | } 26 | 27 | toBytes() { 28 | return padLeft(hexToBuffer(this.bigint.toString(16)), 8); 29 | } 30 | 31 | value(): bigint { 32 | return this.bigint; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/serializable/primitives/boolean.ts: -------------------------------------------------------------------------------- 1 | import { bufferToBool, hexToBuffer, padLeft } from '../../utils/buffer'; 2 | import { serializable } from '../common/types'; 3 | import { Primitives } from './primatives'; 4 | import { TypeSymbols } from '../constants'; 5 | 6 | /** 7 | * Number of bytes per bool. 8 | */ 9 | export const BOOL_LEN = 1; 10 | 11 | @serializable() 12 | export class Bool extends Primitives { 13 | _type = TypeSymbols.Bool; 14 | constructor(private readonly bool: boolean) { 15 | super(); 16 | } 17 | 18 | static fromBytes(buf: Uint8Array): [Bool, Uint8Array] { 19 | return [ 20 | new Bool(bufferToBool(buf.slice(0, BOOL_LEN))), 21 | buf.slice(BOOL_LEN), 22 | ]; 23 | } 24 | 25 | toJSON() { 26 | return this.bool.toString(); 27 | } 28 | 29 | toBytes() { 30 | return padLeft(hexToBuffer(this.bool ? '1' : '0'), BOOL_LEN); 31 | } 32 | 33 | value() { 34 | return this.bool; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/serializable/primitives/byte.ts: -------------------------------------------------------------------------------- 1 | import { bufferToHex } from '../../utils/buffer'; 2 | import { serializable } from '../common/types'; 3 | import { Primitives } from './primatives'; 4 | import { TypeSymbols } from '../constants'; 5 | 6 | @serializable() 7 | export class Byte extends Primitives { 8 | _type = TypeSymbols.Byte; 9 | constructor(private readonly byte: Uint8Array) { 10 | super(); 11 | } 12 | 13 | static fromBytes(buf: Uint8Array): [Byte, Uint8Array] { 14 | return [new Byte(buf.slice(0, 1)), buf.slice(1)]; 15 | } 16 | 17 | toJSON() { 18 | return bufferToHex(this.byte); 19 | } 20 | 21 | toBytes() { 22 | return this.byte; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/serializable/primitives/bytes.test.ts: -------------------------------------------------------------------------------- 1 | import { bytes, bytesBytes } from '../../fixtures/primitives'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { Bytes } from './bytes'; 4 | 5 | testSerialization('Bytes', Bytes, bytes, bytesBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/primitives/bytes.ts: -------------------------------------------------------------------------------- 1 | import { bytesToString } from '@scure/base'; 2 | import { bytesForInt } from '../../fixtures/utils/bytesFor'; 3 | import { bufferToHex, concatBytes } from '../../utils/buffer'; 4 | import { serializable } from '../common/types'; 5 | import { Int } from './int'; 6 | import { Primitives } from './primatives'; 7 | import { TypeSymbols } from '../constants'; 8 | 9 | @serializable() 10 | export class Bytes extends Primitives { 11 | _type = TypeSymbols.Bytes; 12 | constructor(public readonly bytes: Uint8Array) { 13 | super(); 14 | } 15 | 16 | toString(encoding: 'utf8' | 'hex' = 'utf8') { 17 | return bytesToString(encoding, this.bytes); 18 | } 19 | 20 | toJSON() { 21 | return bufferToHex(this.bytes); 22 | } 23 | 24 | static fromBytes(buf: Uint8Array): [Bytes, Uint8Array] { 25 | const [len, remaining] = Int.fromBytes(buf); 26 | 27 | return [ 28 | new Bytes(remaining.slice(0, len.value())), 29 | remaining.slice(len.value()), 30 | ]; 31 | } 32 | 33 | toBytes() { 34 | return concatBytes(bytesForInt(this.bytes.length), this.bytes); 35 | } 36 | 37 | /** 38 | * Returns the length of the bytes (Uint8Array). 39 | * 40 | * Useful for calculating tx complexity. 41 | */ 42 | get length() { 43 | return this.bytes.length; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/primitives/index.ts: -------------------------------------------------------------------------------- 1 | import { BigIntPr } from './bigintpr'; 2 | import { Bool } from './boolean'; 3 | import { Byte } from './byte'; 4 | import { Bytes } from './bytes'; 5 | import { Int } from './int'; 6 | import { Short } from './short'; 7 | import { Stringpr } from './stringpr'; 8 | 9 | export { BigIntPr, Bool, Bytes, Int, Byte, Short, Stringpr }; 10 | -------------------------------------------------------------------------------- /src/serializable/primitives/int.test.ts: -------------------------------------------------------------------------------- 1 | import { int, intBytes } from '../../fixtures/primitives'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { Int } from './int'; 4 | 5 | testSerialization('Int', Int, int, intBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/primitives/int.ts: -------------------------------------------------------------------------------- 1 | import { customInspectSymbol } from '../../constants/node'; 2 | import { bufferToNumber, hexToBuffer, padLeft } from '../../utils/buffer'; 3 | import { serializable } from '../common/types'; 4 | import { Primitives } from './primatives'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | /** 8 | * Number of bytes per int. 9 | */ 10 | export const INT_LEN = 4; 11 | 12 | @serializable() 13 | export class Int extends Primitives { 14 | _type = TypeSymbols.Int; 15 | constructor(private readonly int: number) { 16 | super(); 17 | } 18 | 19 | static fromBytes(buf: Uint8Array): [Int, Uint8Array] { 20 | return [new Int(bufferToNumber(buf.slice(0, INT_LEN))), buf.slice(INT_LEN)]; 21 | } 22 | 23 | [customInspectSymbol]() { 24 | return this.value(); 25 | } 26 | 27 | toJSON() { 28 | return this.int; 29 | } 30 | 31 | toBytes() { 32 | return padLeft(hexToBuffer(this.int.toString(16)), INT_LEN); 33 | } 34 | 35 | value() { 36 | return this.int; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/serializable/primitives/primatives.ts: -------------------------------------------------------------------------------- 1 | export abstract class Primitives { 2 | abstract toJSON(): any; 3 | } 4 | -------------------------------------------------------------------------------- /src/serializable/primitives/short.test.ts: -------------------------------------------------------------------------------- 1 | import { short, shortBytes } from '../../fixtures/primitives'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { Short } from './short'; 4 | 5 | testSerialization('Short', Short, short, shortBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/primitives/short.ts: -------------------------------------------------------------------------------- 1 | import { bufferToNumber, hexToBuffer, padLeft } from '../../utils/buffer'; 2 | import { serializable } from '../common/types'; 3 | import { Primitives } from './primatives'; 4 | import { TypeSymbols } from '../constants'; 5 | 6 | /** 7 | * Number of bytes per short. 8 | */ 9 | export const SHORT_LEN = 2; 10 | 11 | @serializable() 12 | export class Short extends Primitives { 13 | _type = TypeSymbols.Short; 14 | constructor(private readonly short: number) { 15 | super(); 16 | } 17 | 18 | static fromBytes(buf: Uint8Array): [Short, Uint8Array] { 19 | return [ 20 | new Short(bufferToNumber(buf.slice(0, SHORT_LEN))), 21 | buf.slice(SHORT_LEN), 22 | ]; 23 | } 24 | 25 | toJSON() { 26 | return this.short.toString(); 27 | } 28 | 29 | toBytes() { 30 | return padLeft(hexToBuffer(this.short.toString(16)), SHORT_LEN); 31 | } 32 | 33 | value() { 34 | return this.short; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/serializable/primitives/stringpr.test.ts: -------------------------------------------------------------------------------- 1 | import { stringPr, stringPrBytes } from '../../fixtures/primitives'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { Stringpr } from './stringpr'; 4 | 5 | testSerialization('Stringpr', Stringpr, stringPr, stringPrBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/primitives/stringpr.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes, hexToBuffer, padLeft } from '../../utils/buffer'; 2 | import { serializable } from '../common/types'; 3 | import { Primitives } from './primatives'; 4 | import { Short } from './short'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | @serializable() 8 | export class Stringpr extends Primitives { 9 | _type = TypeSymbols.StringPr; 10 | constructor(private readonly string: string) { 11 | super(); 12 | } 13 | 14 | static fromBytes(buf: Uint8Array): [Stringpr, Uint8Array] { 15 | const [length, remaining] = Short.fromBytes(buf); 16 | return [ 17 | new Stringpr( 18 | new TextDecoder().decode(remaining.slice(0, length.value())), 19 | ), 20 | remaining.slice(length.value()), 21 | ]; 22 | } 23 | 24 | toJSON() { 25 | return this.string; 26 | } 27 | 28 | toBytes() { 29 | return concatBytes( 30 | padLeft(hexToBuffer(this.string.length.toString(16)), 2), 31 | new TextEncoder().encode(this.string), 32 | ); 33 | } 34 | 35 | value() { 36 | return this.string; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/serializable/pvm/abstractSubnetTx.ts: -------------------------------------------------------------------------------- 1 | import type { Serializable } from '../common/types'; 2 | import type { Id } from '../fxs/common'; 3 | import type { Input } from '../fxs/secp256k1'; 4 | import { PVMTx } from './abstractTx'; 5 | 6 | export abstract class AbstractSubnetTx extends PVMTx { 7 | abstract subnetAuth: Serializable; 8 | 9 | abstract getSubnetID(): Id; 10 | 11 | getSubnetAuth() { 12 | return this.subnetAuth as Input; 13 | } 14 | 15 | getSigIndices(): number[][] { 16 | return [ 17 | ...this.getInputs().map((input) => { 18 | return input.sigIndicies(); 19 | }), 20 | this.getSubnetAuth().values(), 21 | ].filter((indicies): indicies is number[] => indicies !== undefined); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/serializable/pvm/abstractTx.ts: -------------------------------------------------------------------------------- 1 | import { AvaxTx } from '../avax/avaxTx'; 2 | import { PVM } from '../constants'; 3 | 4 | export abstract class PVMTx extends AvaxTx { 5 | vm = PVM; 6 | } 7 | -------------------------------------------------------------------------------- /src/serializable/pvm/addDelegatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { addDelegatorTx, addDelegatorTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { AddDelegatorTx } from './addDelegatorTx'; 5 | 6 | testSerialization( 7 | 'AddDelegatorTx', 8 | AddDelegatorTx, 9 | addDelegatorTx, 10 | addDelegatorTxBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/addPermissionlessDelegatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | addPermissionlessDelegatorTx, 4 | addPermissionlessDelegatorTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { AddPermissionlessDelegatorTx } from './addPermissionlessDelegatorTx'; 8 | 9 | testSerialization( 10 | 'AddPermissionlessDelegatorTx', 11 | AddPermissionlessDelegatorTx, 12 | addPermissionlessDelegatorTx, 13 | addPermissionlessDelegatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/addPermissionlessValidatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | addPermissionlessValidatorTx, 4 | addPermissionlessValidatorTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { AddPermissionlessValidatorTx } from './addPermissionlessValidatorTx'; 8 | 9 | testSerialization( 10 | 'AddPermissionlessValidatorTx', 11 | AddPermissionlessValidatorTx, 12 | addPermissionlessValidatorTx, 13 | addPermissionlessValidatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/addSubnetValidatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | addSubnetValidatorTx, 4 | addSubnetValidatorTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { AddSubnetValidatorTx } from './addSubnetValidatorTx'; 8 | 9 | testSerialization( 10 | 'AddSubnetValidatorTx', 11 | AddSubnetValidatorTx, 12 | addSubnetValidatorTx, 13 | addSubnetValidatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/addValidatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { addValidatorTx, addValidatorTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { AddValidatorTx } from './addValidatorTx'; 5 | 6 | testSerialization( 7 | 'AddValidatorTx', 8 | AddValidatorTx, 9 | addValidatorTx, 10 | addValidatorTxBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/advanceTimeTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { advanceTimeBytesTx, advanceTimeTx } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { AdvanceTimeTx } from './advanceTimeTx'; 5 | 6 | testSerialization( 7 | 'AdvanceTime', 8 | AdvanceTimeTx, 9 | advanceTimeTx, 10 | advanceTimeBytesTx, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/advanceTimeTx.ts: -------------------------------------------------------------------------------- 1 | import { packSwitched, unpack } from '../../utils/struct'; 2 | import type { Codec } from '../codec/codec'; 3 | import { serializable } from '../common/types'; 4 | import { BigIntPr } from '../primitives'; 5 | import { PVMTx } from './abstractTx'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | @serializable() 9 | export class AdvanceTimeTx extends PVMTx { 10 | _type = TypeSymbols.AdvanceTimeTx; 11 | 12 | constructor(public readonly time: BigIntPr) { 13 | super(); 14 | } 15 | baseTx = undefined; 16 | 17 | static fromBytes( 18 | bytes: Uint8Array, 19 | codec: Codec, 20 | ): [AdvanceTimeTx, Uint8Array] { 21 | const [time, rest] = unpack(bytes, [BigIntPr], codec); 22 | return [new AdvanceTimeTx(time), rest]; 23 | } 24 | 25 | toBytes(codec: Codec) { 26 | return packSwitched(codec, this.time); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/serializable/pvm/baseTx.test.ts: -------------------------------------------------------------------------------- 1 | import { pvmBaseTx, pvmBaseTxBytes } from '../../fixtures/avax'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { BaseTx } from './baseTx'; 4 | 5 | testSerialization('BaseTx', BaseTx, pvmBaseTx, pvmBaseTxBytes); 6 | -------------------------------------------------------------------------------- /src/serializable/pvm/baseTx.ts: -------------------------------------------------------------------------------- 1 | import { unpack } from '../../utils/struct'; 2 | import { BaseTx as AvaxBaseTx } from '../avax/baseTx'; 3 | import type { Codec } from '../codec/codec'; 4 | import { serializable } from '../common/types'; 5 | import { PVMTx } from './abstractTx'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/23-p-chain-native-transfers.md 10 | * TODO: add doc reference after D-upgrade 11 | */ 12 | @serializable() 13 | export class BaseTx extends PVMTx { 14 | _type = TypeSymbols.PvmBaseTx; 15 | 16 | constructor(public readonly baseTx: AvaxBaseTx) { 17 | super(); 18 | } 19 | 20 | static fromBytes(bytes: Uint8Array, codec: Codec): [BaseTx, Uint8Array] { 21 | const [baseTx, remaining] = unpack(bytes, [AvaxBaseTx], codec); 22 | return [new BaseTx(baseTx), remaining]; 23 | } 24 | 25 | toBytes(codec: Codec) { 26 | return this.baseTx.toBytes(codec); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/serializable/pvm/convertSubnetToL1Tx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | convertSubnetToL1Tx, 4 | convertSubnetToL1TxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { ConvertSubnetToL1Tx } from './convertSubnetToL1Tx'; 8 | 9 | testSerialization( 10 | 'ConvertSubnetToL1Tx', 11 | ConvertSubnetToL1Tx, 12 | convertSubnetToL1Tx, 13 | convertSubnetToL1TxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/createChainTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { createChainTx, createChainTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { CreateChainTx } from './createChainTx'; 5 | 6 | testSerialization( 7 | 'CreateChainTx', 8 | CreateChainTx, 9 | createChainTx, 10 | createChainTxBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/createSubnetTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { createSubnetTx, createSubnetTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { CreateSubnetTx } from './createSubnetTx'; 5 | 6 | testSerialization( 7 | 'CreateSubnetTx', 8 | CreateSubnetTx, 9 | createSubnetTx, 10 | createSubnetTxBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/disableL1ValidatorTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | disableL1ValidatorTx, 4 | disableL1ValidatorTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { DisableL1ValidatorTx } from './disableL1ValidatorTx'; 8 | 9 | testSerialization( 10 | 'DisableL1ValidatorTx', 11 | DisableL1ValidatorTx, 12 | disableL1ValidatorTx, 13 | disableL1ValidatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/exportTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { exportTx, exportTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { ExportTx } from './exportTx'; 5 | 6 | testSerialization('ExportTx', ExportTx, exportTx, exportTxBytes, testPVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/pvm/exportTx.ts: -------------------------------------------------------------------------------- 1 | import { toListStruct } from '../../utils/serializeList'; 2 | import { packSwitched, unpack } from '../../utils/struct'; 3 | import { BaseTx } from '../avax/baseTx'; 4 | import { TransferableOutput } from '../avax/transferableOutput'; 5 | import type { Codec } from '../codec/codec'; 6 | import { serializable } from '../common/types'; 7 | import { Id } from '../fxs/common'; 8 | import { PVMTx } from './abstractTx'; 9 | import { TypeSymbols } from '../constants'; 10 | 11 | /** 12 | * @see 13 | */ 14 | @serializable() 15 | export class ExportTx extends PVMTx { 16 | _type = TypeSymbols.PvmExportTx; 17 | 18 | constructor( 19 | public readonly baseTx: BaseTx, 20 | public readonly destination: Id, 21 | public readonly outs: TransferableOutput[], 22 | ) { 23 | super(); 24 | } 25 | 26 | static fromBytes(bytes: Uint8Array, codec: Codec): [ExportTx, Uint8Array] { 27 | const [baseTx, id, outs, rest] = unpack( 28 | bytes, 29 | [BaseTx, Id, toListStruct(TransferableOutput)], 30 | codec, 31 | ); 32 | return [new ExportTx(baseTx, id, outs), rest]; 33 | } 34 | 35 | toBytes(codec: Codec) { 36 | return packSwitched(codec, this.baseTx, this.destination, this.outs); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/serializable/pvm/importTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { importTx, importTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { ImportTx } from './importTx'; 5 | 6 | testSerialization('ImportTx', ImportTx, importTx, importTxBytes, testPVMCodec); 7 | -------------------------------------------------------------------------------- /src/serializable/pvm/importTx.ts: -------------------------------------------------------------------------------- 1 | import { toListStruct } from '../../utils/serializeList'; 2 | import { pack, unpack } from '../../utils/struct'; 3 | import { BaseTx } from '../avax/baseTx'; 4 | import { TransferableInput } from '../avax/transferableInput'; 5 | import type { Codec } from '../codec/codec'; 6 | import { serializable } from '../common/types'; 7 | import { Id } from '../fxs/common'; 8 | import { PVMTx } from './abstractTx'; 9 | import { TypeSymbols } from '../constants'; 10 | 11 | /** 12 | * @see 13 | */ 14 | @serializable() 15 | export class ImportTx extends PVMTx { 16 | _type = TypeSymbols.PvmImportTx; 17 | 18 | constructor( 19 | public readonly baseTx: BaseTx, 20 | public readonly sourceChain: Id, 21 | public readonly ins: TransferableInput[], 22 | ) { 23 | super(); 24 | } 25 | 26 | getSigIndices() { 27 | return this.ins.map((inp) => inp.sigIndicies()); 28 | } 29 | 30 | static fromBytes(bytes: Uint8Array, codec: Codec): [ImportTx, Uint8Array] { 31 | const [baseTx, sourceChain, ins, rest] = unpack( 32 | bytes, 33 | [BaseTx, Id, toListStruct(TransferableInput)], 34 | codec, 35 | ); 36 | return [new ImportTx(baseTx, sourceChain, ins), rest]; 37 | } 38 | 39 | toBytes(codec: Codec) { 40 | return pack([this.baseTx, this.sourceChain, this.ins], codec); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/serializable/pvm/increaseL1ValidatorBalance.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | increaseL1ValidatorBalanceTx, 4 | increaseL1ValidatorBalanceTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { IncreaseL1ValidatorBalanceTx } from './increaseL1ValidatorBalanceTx'; 8 | 9 | testSerialization( 10 | 'IncreaseL1ValidatorBalanceTx', 11 | IncreaseL1ValidatorBalanceTx, 12 | increaseL1ValidatorBalanceTx, 13 | increaseL1ValidatorBalanceTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/increaseL1ValidatorBalanceTx.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../utils/struct'; 2 | import { BaseTx } from '../avax/baseTx'; 3 | import type { Codec } from '../codec'; 4 | import { serializable } from '../common/types'; 5 | import { TypeSymbols } from '../constants'; 6 | import { Id } from '../fxs/common'; 7 | import { BigIntPr } from '../primitives'; 8 | import { PVMTx } from './abstractTx'; 9 | 10 | @serializable() 11 | export class IncreaseL1ValidatorBalanceTx extends PVMTx { 12 | _type = TypeSymbols.IncreaseL1ValidatorBalanceTx; 13 | 14 | constructor( 15 | public readonly baseTx: BaseTx, 16 | /** 17 | * The corresponding Validator ID. 18 | */ 19 | public readonly validationId: Id, 20 | /** 21 | * Balance <= sum of AVAX inputs - sum of AVAX outputs - Tx fee 22 | */ 23 | public readonly balance: BigIntPr, 24 | ) { 25 | super(); 26 | } 27 | 28 | static fromBytes( 29 | bytes: Uint8Array, 30 | codec: Codec, 31 | ): [increaseBalanceTx: IncreaseL1ValidatorBalanceTx, rest: Uint8Array] { 32 | const [baseTx, validationId, balance, rest] = unpack( 33 | bytes, 34 | [BaseTx, Id, BigIntPr], 35 | codec, 36 | ); 37 | 38 | return [ 39 | new IncreaseL1ValidatorBalanceTx(baseTx, validationId, balance), 40 | rest, 41 | ]; 42 | } 43 | 44 | toBytes(codec: Codec) { 45 | return pack([this.baseTx, this.validationId, this.balance], codec); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/serializable/pvm/proofOfPossession.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../common/types'; 2 | import { bufferToHex, concatBytes } from '../../utils/buffer'; 3 | import { bls } from '../../crypto'; 4 | import { TypeSymbols } from '../constants'; 5 | 6 | /** 7 | * @see https://docs.avax.network/specs/platform-transaction-serialization#proof-of-possession-specification-1 8 | */ 9 | @serializable() 10 | export class ProofOfPossession { 11 | _type = TypeSymbols.ProofOfPossession; 12 | 13 | constructor( 14 | public readonly publicKey: Uint8Array, 15 | public readonly signature: Uint8Array, 16 | ) { 17 | const pk = bls.publicKeyFromBytes(publicKey); 18 | const sig = bls.signatureFromBytes(signature); 19 | 20 | pk.assertValidity(); 21 | sig.assertValidity(); 22 | 23 | if (!bls.verifyProofOfPossession(pk, sig, bls.publicKeyToBytes(pk))) { 24 | throw new Error(`Invalid proof of possession`); 25 | } 26 | } 27 | 28 | static fromBytes(bytes: Uint8Array): [ProofOfPossession, Uint8Array] { 29 | const pubkey = bytes.slice(0, bls.PUBLIC_KEY_LENGTH); 30 | const signature = bytes.slice( 31 | bls.PUBLIC_KEY_LENGTH, 32 | bls.PUBLIC_KEY_LENGTH + bls.SIGNATURE_LENGTH, 33 | ); 34 | const rest = bytes.slice(bls.PUBLIC_KEY_LENGTH + bls.SIGNATURE_LENGTH); 35 | return [new ProofOfPossession(pubkey, signature), rest]; 36 | } 37 | 38 | toString() { 39 | return bufferToHex(this.toBytes()); 40 | } 41 | 42 | toBytes() { 43 | return concatBytes(this.publicKey, this.signature); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/pvm/registerL1Validator.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | registerL1ValidatorTx, 4 | registerL1ValidatorTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { RegisterL1ValidatorTx } from './registerL1ValidatorTx'; 8 | 9 | testSerialization( 10 | 'RegisterL1ValidatorTx', 11 | RegisterL1ValidatorTx, 12 | registerL1ValidatorTx, 13 | registerL1ValidatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/registerL1ValidatorTx.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../utils/struct'; 2 | import { BaseTx } from '../avax/baseTx'; 3 | import type { Codec } from '../codec/codec'; 4 | import { serializable } from '../common/types'; 5 | import { TypeSymbols } from '../constants'; 6 | import { BlsSignature } from '../fxs/common/blsSignature'; 7 | import { BigIntPr, Bytes } from '../primitives'; 8 | import { PVMTx } from './abstractTx'; 9 | 10 | @serializable() 11 | export class RegisterL1ValidatorTx extends PVMTx { 12 | _type = TypeSymbols.RegisterL1ValidatorTx; 13 | 14 | constructor( 15 | public readonly baseTx: BaseTx, 16 | public readonly balance: BigIntPr, 17 | public readonly blsSignature: BlsSignature, 18 | public readonly message: Bytes, 19 | ) { 20 | super(); 21 | } 22 | 23 | static fromBytes( 24 | bytes: Uint8Array, 25 | codec: Codec, 26 | ): [registerSubnetValidatorTx: RegisterL1ValidatorTx, rest: Uint8Array] { 27 | const [baseTx, balance, blsSignature, message, rest] = unpack( 28 | bytes, 29 | [BaseTx, BigIntPr, BlsSignature, Bytes], 30 | codec, 31 | ); 32 | 33 | return [ 34 | new RegisterL1ValidatorTx(baseTx, balance, blsSignature, message), 35 | rest, 36 | ]; 37 | } 38 | 39 | toBytes(codec: Codec) { 40 | return pack( 41 | [this.baseTx, this.balance, this.blsSignature, this.message], 42 | codec, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/pvm/removeSubnetValidator.test.ts: -------------------------------------------------------------------------------- 1 | import { RemoveSubnetValidatorTx } from './removeSubnetValidatorTx'; 2 | import { testSerialization } from '../../fixtures/utils/serializable'; 3 | import { 4 | removeSubnetValidatorTxBytes, 5 | removeValidatorTx, 6 | } from '../../fixtures/pvm'; 7 | import { testPVMCodec } from '../../fixtures/codec'; 8 | 9 | testSerialization( 10 | 'RemoveSubnetValidator', 11 | RemoveSubnetValidatorTx, 12 | removeValidatorTx, 13 | removeSubnetValidatorTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/rewardValidatorTx.ts: -------------------------------------------------------------------------------- 1 | import { packSwitched, unpack } from '../../utils/struct'; 2 | import type { Codec } from '../codec/codec'; 3 | import { serializable } from '../common/types'; 4 | import { Id } from '../fxs/common'; 5 | import { PVMTx } from './abstractTx'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see 10 | */ 11 | @serializable() 12 | export class RewardValidatorTx extends PVMTx { 13 | _type = TypeSymbols.RewardValidatorTx; 14 | 15 | constructor(public readonly txId: Id) { 16 | super(); 17 | } 18 | 19 | baseTx = undefined; 20 | 21 | static fromBytes( 22 | bytes: Uint8Array, 23 | codec: Codec, 24 | ): [RewardValidatorTx, Uint8Array] { 25 | const [txId, rest] = unpack(bytes, [Id], codec); 26 | return [new RewardValidatorTx(txId), rest]; 27 | } 28 | 29 | toBytes(codec: Codec) { 30 | return packSwitched(codec, this.txId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/serializable/pvm/setL1ValidatorWeightTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | setL1ValidatorWeightTx, 4 | setL1ValidatorWeightTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { SetL1ValidatorWeightTx } from './setL1ValidatorWeightTx'; 8 | 9 | testSerialization( 10 | 'SetL1ValidatorWeightTx', 11 | SetL1ValidatorWeightTx, 12 | setL1ValidatorWeightTx, 13 | setL1ValidatorWeightTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/setL1ValidatorWeightTx.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../utils/struct'; 2 | import { BaseTx } from '../avax/baseTx'; 3 | import type { Codec } from '../codec'; 4 | import { serializable } from '../common/types'; 5 | import { TypeSymbols } from '../constants'; 6 | import { Bytes } from '../primitives'; 7 | import { PVMTx } from './abstractTx'; 8 | 9 | @serializable() 10 | export class SetL1ValidatorWeightTx extends PVMTx { 11 | _type = TypeSymbols.SetL1ValidatorWeightTx; 12 | 13 | constructor(public readonly baseTx: BaseTx, public readonly message: Bytes) { 14 | super(); 15 | } 16 | 17 | static fromBytes( 18 | bytes: Uint8Array, 19 | codec: Codec, 20 | ): [SetL1ValidatorWeightTx, Uint8Array] { 21 | const [baseTx, message, rest] = unpack(bytes, [BaseTx, Bytes], codec); 22 | 23 | return [new SetL1ValidatorWeightTx(baseTx, message), rest]; 24 | } 25 | 26 | toBytes(codec: Codec): Uint8Array { 27 | return pack([this.baseTx, this.message], codec); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/serializable/pvm/signer.ts: -------------------------------------------------------------------------------- 1 | import { serializable } from '../common/types'; 2 | import { ProofOfPossession } from './proofOfPossession'; 3 | import { pack, unpack } from '../../utils/struct'; 4 | import type { Codec } from '../codec'; 5 | import { TypeSymbols } from '../constants'; 6 | 7 | @serializable() 8 | export class Signer { 9 | _type = TypeSymbols.Signer; 10 | 11 | constructor(public readonly proof: ProofOfPossession) {} 12 | 13 | static fromBytes(bytes: Uint8Array, codec: Codec): [Signer, Uint8Array] { 14 | const [proof, rest] = unpack(bytes, [ProofOfPossession], codec); 15 | return [new Signer(proof), rest]; 16 | } 17 | 18 | toBytes(codec: Codec) { 19 | return pack([this.proof], codec); 20 | } 21 | } 22 | 23 | @serializable() 24 | export class SignerEmpty { 25 | _type = TypeSymbols.SignerEmpty; 26 | 27 | static fromBytes(bytes: Uint8Array, codec: Codec): [SignerEmpty, Uint8Array] { 28 | const [rest] = unpack(bytes, [], codec); 29 | return [new SignerEmpty(), rest]; 30 | } 31 | 32 | toBytes(codec: Codec) { 33 | return pack([], codec); 34 | } 35 | } 36 | 37 | export function createSignerOrSignerEmptyFromStrings( 38 | publicKey?: Uint8Array, 39 | signature?: Uint8Array, 40 | ) { 41 | return publicKey && signature 42 | ? new Signer( 43 | new ProofOfPossession( 44 | new Uint8Array(publicKey), 45 | new Uint8Array(signature), 46 | ), 47 | ) 48 | : new SignerEmpty(); 49 | } 50 | -------------------------------------------------------------------------------- /src/serializable/pvm/stakeableLockIn.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { stakeableLockIn, stakeableLockInBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { StakeableLockIn } from './stakeableLockIn'; 5 | 6 | testSerialization( 7 | 'StakeableLockIn', 8 | StakeableLockIn, 9 | stakeableLockIn, 10 | stakeableLockInBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/stakeableLockIn.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '@noble/hashes/utils'; 2 | import { packSwitched, unpack } from '../../utils/struct'; 3 | import type { Codec } from '../codec/codec'; 4 | import type { Amounter } from '../common/types'; 5 | import { serializable } from '../common/types'; 6 | import { BigIntPr } from '../primitives'; 7 | import { TypeSymbols } from '../constants'; 8 | 9 | /** 10 | * @see https://docs.avax.network/specs/platform-transaction-serialization#stakeablelockin 11 | */ 12 | @serializable() 13 | export class StakeableLockIn { 14 | _type = TypeSymbols.StakeableLockIn; 15 | 16 | constructor( 17 | public readonly lockTime: BigIntPr, 18 | public readonly transferableInput: Amounter, 19 | ) {} 20 | 21 | static fromBytes( 22 | bytes: Uint8Array, 23 | codec: Codec, 24 | ): [StakeableLockIn, Uint8Array] { 25 | const [lockTime, rest] = unpack(bytes, [BigIntPr], codec); 26 | 27 | const [transferableInput, remaining] = codec.UnpackPrefix(rest); 28 | 29 | return [new StakeableLockIn(lockTime, transferableInput), remaining]; 30 | } 31 | amount() { 32 | return this.transferableInput.amount(); 33 | } 34 | 35 | toBytes(codec: Codec) { 36 | return concatBytes( 37 | packSwitched(codec, this.lockTime), 38 | codec.PackPrefix(this.transferableInput), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/serializable/pvm/stakeableLockOut.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { stakeableLockOut, stakeableLockOutBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { StakeableLockOut } from './stakeableLockOut'; 5 | 6 | testSerialization( 7 | 'StakeableLockOut', 8 | StakeableLockOut, 9 | stakeableLockOut, 10 | stakeableLockOutBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/subnetValidator.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { subnetValidator, subnetValidatorBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { SubnetValidator } from './subnetValidator'; 5 | 6 | testSerialization( 7 | 'SubnetValidator', 8 | SubnetValidator, 9 | subnetValidator, 10 | subnetValidatorBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/subnetValidator.ts: -------------------------------------------------------------------------------- 1 | import type { Codec } from '../codec/codec'; 2 | import { serializable } from '../common/types'; 3 | import { Id } from '../fxs/common'; 4 | import { pack, unpack } from '../../utils/struct'; 5 | import { Validator } from './validator'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see 10 | */ 11 | @serializable() 12 | export class SubnetValidator { 13 | _type = TypeSymbols.SubnetValidator; 14 | 15 | constructor( 16 | public readonly validator: Validator, 17 | public readonly subnetId: Id, 18 | ) {} 19 | 20 | static fromNative( 21 | nodeId: string, 22 | startTime: bigint, 23 | endTime: bigint, 24 | weight: bigint, 25 | subnetId: Id, 26 | ) { 27 | return new SubnetValidator( 28 | Validator.fromNative(nodeId, startTime, endTime, weight), 29 | subnetId, 30 | ); 31 | } 32 | 33 | static fromBytes( 34 | bytes: Uint8Array, 35 | codec: Codec, 36 | ): [SubnetValidator, Uint8Array] { 37 | const [validator, subnetId, rest] = unpack(bytes, [Validator, Id], codec); 38 | return [new SubnetValidator(validator, subnetId), rest]; 39 | } 40 | 41 | toBytes(codec: Codec) { 42 | return pack([this.validator, this.subnetId], codec); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/serializable/pvm/transferSubnetOwnershipTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { 3 | transferSubnetOwnershipTx, 4 | transferSubnetOwnershipTxBytes, 5 | } from '../../fixtures/pvm'; 6 | import { testSerialization } from '../../fixtures/utils/serializable'; 7 | import { TransferSubnetOwnershipTx } from './transferSubnetOwnershipTx'; 8 | 9 | testSerialization( 10 | 'TransferSubnetOwnershipTx', 11 | TransferSubnetOwnershipTx, 12 | transferSubnetOwnershipTx, 13 | transferSubnetOwnershipTxBytes, 14 | testPVMCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/transformSubnetTx.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { transformSubnetTx, transformSubnetTxBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { TransformSubnetTx } from './transformSubnetTx'; 5 | 6 | testSerialization( 7 | 'TransformSubnetTx', 8 | TransformSubnetTx, 9 | transformSubnetTx, 10 | transformSubnetTxBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/validator.test.ts: -------------------------------------------------------------------------------- 1 | import { testPVMCodec } from '../../fixtures/codec'; 2 | import { validator, validatorBytes } from '../../fixtures/pvm'; 3 | import { testSerialization } from '../../fixtures/utils/serializable'; 4 | import { Validator } from './validator'; 5 | 6 | testSerialization( 7 | 'Validator', 8 | Validator, 9 | validator, 10 | validatorBytes, 11 | testPVMCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/validator.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../utils/struct'; 2 | import type { Codec } from '../codec/codec'; 3 | import { serializable } from '../common/types'; 4 | import { NodeId } from '../fxs/common/nodeId'; 5 | import { BigIntPr } from '../primitives'; 6 | import { TypeSymbols } from '../constants'; 7 | 8 | /** 9 | * @see https://docs.avax.network/specs/platform-transaction-serialization#unsigned-add-validator-tx 10 | */ 11 | @serializable() 12 | export class Validator { 13 | _type = TypeSymbols.Validator; 14 | 15 | constructor( 16 | public readonly nodeId: NodeId, 17 | public readonly startTime: BigIntPr, 18 | public readonly endTime: BigIntPr, 19 | public readonly weight: BigIntPr, 20 | ) {} 21 | 22 | static fromNative( 23 | nodeId: string, 24 | startTime: bigint, 25 | endTime: bigint, 26 | weight: bigint, 27 | ) { 28 | return new Validator( 29 | NodeId.fromString(nodeId), 30 | new BigIntPr(startTime), 31 | new BigIntPr(endTime), 32 | new BigIntPr(weight), 33 | ); 34 | } 35 | 36 | static fromBytes(bytes: Uint8Array, codec: Codec): [Validator, Uint8Array] { 37 | const [nodeId, startTime, endTime, weight, rest] = unpack( 38 | bytes, 39 | [NodeId, BigIntPr, BigIntPr, BigIntPr], 40 | codec, 41 | ); 42 | 43 | return [new Validator(nodeId, startTime, endTime, weight), rest]; 44 | } 45 | 46 | toBytes(codec: Codec) { 47 | return pack( 48 | [this.nodeId, this.startTime, this.endTime, this.weight], 49 | codec, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/addressedCallPayload.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../fixtures/utils/serializable'; 3 | import { addressedCall, addressedCallBytes } from '../../../../fixtures/warp'; 4 | import { AddressedCall } from './addressedCallPayload'; 5 | 6 | testSerialization( 7 | 'AddressedCall', 8 | AddressedCall, 9 | addressedCall, 10 | addressedCallBytes, 11 | testWarpCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addressedCallPayload'; 2 | export * from './messages/registerL1ValidatorMessage'; 3 | export * from './messages/l1ValidatorWeightMessage'; 4 | export * from './messages/l1ValidatorRegistrationMessage'; 5 | export * from './messages/subnetToL1ConversionMessage'; 6 | 7 | export * from './utils/validatorData'; 8 | export * from './utils/conversionData'; 9 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/messages/l1ValidatorRegistrationMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | l1ValidatorRegistrationMessage, 5 | l1ValidatorRegistrationMessageBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { L1ValidatorRegistrationMessage } from './l1ValidatorRegistrationMessage'; 8 | 9 | testSerialization( 10 | 'L1ValidatorRegistrationMessage', 11 | L1ValidatorRegistrationMessage, 12 | l1ValidatorRegistrationMessage, 13 | l1ValidatorRegistrationMessageBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/messages/l1ValidatorWeightMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | l1ValidatorWeightMessage, 5 | l1ValidatorWeightMessageBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { L1ValidatorWeightMessage } from './l1ValidatorWeightMessage'; 8 | 9 | testSerialization( 10 | 'L1ValidatorWeightMessage', 11 | L1ValidatorWeightMessage, 12 | l1ValidatorWeightMessage, 13 | l1ValidatorWeightMessageBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/messages/registerL1ValidatorMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | registerL1ValidatorMessage, 5 | registerL1ValidatorMessageBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { RegisterL1ValidatorMessage } from './registerL1ValidatorMessage'; 8 | 9 | testSerialization( 10 | 'RegisterL1ValidatorMessage', 11 | RegisterL1ValidatorMessage, 12 | registerL1ValidatorMessage, 13 | registerL1ValidatorMessageBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/messages/subnetToL1ConversionMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | subnetToL1ConversionMessage, 5 | subnetToL1ConversionMessageBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { SubnetToL1ConversionMessage } from './subnetToL1ConversionMessage'; 8 | 9 | testSerialization( 10 | 'SubnetToL1ConversionMessage', 11 | SubnetToL1ConversionMessage, 12 | subnetToL1ConversionMessage, 13 | subnetToL1ConversionMessageBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/utils/conversionData.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | conversionData, 5 | conversionDataBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { ConversionData } from './conversionData'; 8 | 9 | testSerialization( 10 | 'ConversionData', 11 | ConversionData, 12 | conversionData, 13 | conversionDataBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/utils/validatorData.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../../../fixtures/codec'; 2 | import { testSerialization } from '../../../../../fixtures/utils/serializable'; 3 | import { 4 | validatorData, 5 | validatorDataBytes, 6 | } from '../../../../../fixtures/warp'; 7 | import { ValidatorData } from './validatorData'; 8 | 9 | testSerialization( 10 | 'ValidatorData', 11 | ValidatorData, 12 | validatorData, 13 | validatorDataBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/addressedCall/utils/validatorData.ts: -------------------------------------------------------------------------------- 1 | import { BlsPublicKey, NodeId } from '../../../../fxs/common'; 2 | import { unpack, pack } from '../../../../../utils/struct'; 3 | import type { Codec } from '../../../../codec'; 4 | import { serializable } from '../../../../common/types'; 5 | import { TypeSymbols } from '../../../../constants'; 6 | import { BigIntPr, Bytes } from '../../../../primitives'; 7 | 8 | /** 9 | * The `ValidatorData` is a structure that contains the data for the initial validator set. 10 | * It is used in the `SubnetToL1ConversionMessage`. 11 | * 12 | * Ref: https://github.com/avalanche-foundation/ACPs/blob/58c78c/ACPs/77-reinventing-subnets/README.md#subnettol1conversionmessage 13 | */ 14 | @serializable() 15 | export class ValidatorData { 16 | _type = TypeSymbols.ValidatorData; 17 | 18 | constructor( 19 | public readonly nodeId: NodeId, 20 | public readonly blsPublicKey: BlsPublicKey, 21 | public readonly weight: BigIntPr, 22 | ) {} 23 | 24 | static fromBytes( 25 | bytes: Uint8Array, 26 | codec: Codec, 27 | ): [ValidatorData, Uint8Array] { 28 | const [nodeIdBytes, blsPublicKey, weight, rest] = unpack( 29 | bytes, 30 | [Bytes, BlsPublicKey, BigIntPr], 31 | codec, 32 | ); 33 | const nodeId = new NodeId(nodeIdBytes.bytes); 34 | 35 | return [new ValidatorData(nodeId, blsPublicKey, weight), rest]; 36 | } 37 | 38 | toBytes(codec: Codec) { 39 | const nodeIdBytes = new Bytes(this.nodeId.toBytes()); 40 | return pack([nodeIdBytes, this.blsPublicKey, this.weight], codec); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec, Manager } from '../../codec'; 2 | import { WarpSignature } from './signature'; 3 | 4 | /** 5 | * @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/codec.go 6 | */ 7 | export const codec = new Codec([WarpSignature]); 8 | 9 | let manager: Manager; 10 | export const getWarpManager = () => { 11 | if (manager) return manager; 12 | manager = new Manager(); 13 | manager.RegisterCodec(0, codec); 14 | return manager; 15 | }; 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/index.ts: -------------------------------------------------------------------------------- 1 | export { codec, getWarpManager } from './codec'; 2 | export { WarpMessage } from './message'; 3 | export * as AddressedCallPayloads from './addressedCall'; 4 | export { WarpSignature } from './signature'; 5 | export { WarpUnsignedMessage } from './unsignedMessage'; 6 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/message.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../fixtures/codec'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { warpMessage, warpMessageBytes } from '../../../fixtures/warp'; 4 | import { WarpMessage } from './message'; 5 | 6 | testSerialization( 7 | 'WarpMessage', 8 | WarpMessage, 9 | warpMessage, 10 | warpMessageBytes, 11 | testWarpCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/message.ts: -------------------------------------------------------------------------------- 1 | import { concatBytes } from '../../../utils/buffer'; 2 | import { unpack } from '../../../utils/struct'; 3 | import type { Codec } from '../../codec'; 4 | import { serializable } from '../../common/types'; 5 | import { TypeSymbols } from '../../constants'; 6 | import type { WarpSignature } from './signature'; 7 | import { WarpUnsignedMessage } from './unsignedMessage'; 8 | 9 | @serializable() 10 | export class WarpMessage { 11 | _type = TypeSymbols.WarpMessage; 12 | 13 | constructor( 14 | public readonly unsignedMessage: WarpUnsignedMessage, 15 | public readonly signature: WarpSignature, 16 | ) {} 17 | 18 | static fromBytes(bytes: Uint8Array, codec: Codec): [WarpMessage, Uint8Array] { 19 | const [unsignedMessage, signatureBytes] = unpack( 20 | bytes, 21 | [WarpUnsignedMessage], 22 | codec, 23 | ); 24 | 25 | const [signature, rest] = codec.UnpackPrefix(signatureBytes); 26 | 27 | return [new WarpMessage(unsignedMessage, signature), rest]; 28 | } 29 | 30 | toBytes(codec: Codec) { 31 | return concatBytes( 32 | this.unsignedMessage.toBytes(codec), 33 | codec.PackPrefix(this.signature), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/signature.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../fixtures/codec'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { warpSignature, warpSignatureBytes } from '../../../fixtures/warp'; 4 | import { WarpSignature } from './signature'; 5 | 6 | testSerialization( 7 | 'WarpSignature', 8 | WarpSignature, 9 | warpSignature, 10 | warpSignatureBytes, 11 | testWarpCodec, 12 | ); 13 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/signature.ts: -------------------------------------------------------------------------------- 1 | import { hammingWeight } from '../../../utils/buffer'; 2 | import { pack, unpack } from '../../../utils/struct'; 3 | import type { Codec } from '../../codec'; 4 | import { serializable } from '../../common/types'; 5 | import { TypeSymbols } from '../../constants'; 6 | import { BlsSignature } from '../../fxs/common'; 7 | import { Bytes } from '../../primitives'; 8 | 9 | @serializable() 10 | export class WarpSignature { 11 | _type = TypeSymbols.WarpSignature; 12 | 13 | constructor( 14 | public readonly signers: Bytes, 15 | public readonly signature: BlsSignature, 16 | ) {} 17 | 18 | static fromBytes( 19 | bytes: Uint8Array, 20 | codec: Codec, 21 | ): [WarpSignature, Uint8Array] { 22 | const [signers, signature, rest] = unpack( 23 | bytes, 24 | [Bytes, BlsSignature], 25 | codec, 26 | ); 27 | 28 | return [new WarpSignature(signers, signature), rest]; 29 | } 30 | 31 | toBytes(codec: Codec) { 32 | return pack([this.signers, this.signature], codec); 33 | } 34 | 35 | /** 36 | * Number of BLS public keys that participated in the 37 | * {@linkcode BlsSignature}. This is exposed because users of the signatures 38 | * typically impose a verification fee that is a function of the number of signers. 39 | * 40 | * This is used to calculate the Warp complexity in transactions. 41 | */ 42 | numOfSigners(): number { 43 | return hammingWeight(this.signers.bytes); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/unsignedMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { testWarpCodec } from '../../../fixtures/codec'; 2 | import { testSerialization } from '../../../fixtures/utils/serializable'; 3 | import { 4 | warpUnsignedMessage, 5 | warpUnsignedMessageBytes, 6 | } from '../../../fixtures/warp'; 7 | import { WarpUnsignedMessage } from './unsignedMessage'; 8 | 9 | testSerialization( 10 | 'WarpUnsignedMessage', 11 | WarpUnsignedMessage, 12 | warpUnsignedMessage, 13 | warpUnsignedMessageBytes, 14 | testWarpCodec, 15 | ); 16 | -------------------------------------------------------------------------------- /src/serializable/pvm/warp/unsignedMessage.ts: -------------------------------------------------------------------------------- 1 | import { pack, unpack } from '../../../utils/struct'; 2 | import type { Codec } from '../../codec'; 3 | import { serializable } from '../../common/types'; 4 | import { TypeSymbols } from '../../constants'; 5 | import { Id } from '../../fxs/common'; 6 | import { Bytes, Int } from '../../primitives'; 7 | 8 | @serializable() 9 | export class WarpUnsignedMessage { 10 | _type = TypeSymbols.WarpUnsignedMessage; 11 | 12 | constructor( 13 | public readonly networkId: Int, 14 | public readonly sourceChainId: Id, 15 | public readonly payload: Bytes, 16 | ) {} 17 | 18 | static fromBytes( 19 | bytes: Uint8Array, 20 | codec: Codec, 21 | ): [WarpUnsignedMessage, Uint8Array] { 22 | const [networkId, sourceChainId, payload, rest] = unpack( 23 | bytes, 24 | [Int, Id, Bytes], 25 | codec, 26 | ); 27 | 28 | return [new WarpUnsignedMessage(networkId, sourceChainId, payload), rest]; 29 | } 30 | 31 | toBytes(codec: Codec) { 32 | return pack([this.networkId, this.sourceChainId, this.payload], codec); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/signer/addTxSignatures.ts: -------------------------------------------------------------------------------- 1 | import type { UnsignedTx } from '../vms/common/unsignedTx'; 2 | import { secp256k1 } from '../crypto'; 3 | 4 | export const addTxSignatures = async ({ 5 | unsignedTx, 6 | privateKeys, 7 | }: { 8 | unsignedTx: UnsignedTx; 9 | privateKeys: Uint8Array[]; 10 | }) => { 11 | const unsignedBytes = unsignedTx.toBytes(); 12 | 13 | await Promise.all( 14 | privateKeys.map(async (privateKey) => { 15 | const publicKey = secp256k1.getPublicKey(privateKey); 16 | 17 | if (unsignedTx.hasPubkey(publicKey)) { 18 | const signature = await secp256k1.sign(unsignedBytes, privateKey); 19 | unsignedTx.addSignature(signature); 20 | } 21 | }), 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/signer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addTxSignatures'; 2 | -------------------------------------------------------------------------------- /src/utils/UTXOSet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models'; 2 | export * from './UTXOSet'; 3 | -------------------------------------------------------------------------------- /src/utils/UTXOSet/models.ts: -------------------------------------------------------------------------------- 1 | import type { UtxoSet } from './UTXOSet'; 2 | 3 | export interface AssetDict { 4 | [assetID: string]: UtxoSet; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/addChecksum.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha256'; 2 | import { concatBytes } from './buffer'; 3 | 4 | export function addChecksum(data: Uint8Array) { 5 | return concatBytes(data, sha256(data).subarray(-4)); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/address.test.ts: -------------------------------------------------------------------------------- 1 | import { base58check } from './base58'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { secp256k1 } from '../crypto'; 5 | import * as address from './address'; 6 | 7 | describe('address', () => { 8 | it('parses and formats correctly', async () => { 9 | const key = '24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5'; 10 | const privKey = base58check.decode(key); 11 | const pubKey = secp256k1.getPublicKey(privKey); 12 | 13 | const addrBytes = secp256k1.publicKeyBytesToAddress(pubKey); 14 | 15 | const addr = address.format('X', 'avax', addrBytes); 16 | expect(addr).toEqual('X-avax1lnk637g0edwnqc2tn8tel39652fswa3xmgyghf'); 17 | 18 | expect(address.parse(addr)).toEqual(['X', 'avax', addrBytes]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/address.ts: -------------------------------------------------------------------------------- 1 | import { bech32 } from '@scure/base'; 2 | 3 | const addressSep = '-'; 4 | 5 | // parse takes in an address string and returns an array of 6 | // [chain ID alias, bech32 HRP, address bytes]. 7 | export function parse(addrStr: string): [string, string, Uint8Array] { 8 | const parts = addrStr.split(addressSep); 9 | if (parts.length < 2) { 10 | throw new Error(`Invalid address: ${addrStr}`); 11 | } 12 | 13 | const chainID = parts[0]; 14 | const rawAddr = parts[1]; 15 | 16 | const [hrp, addr] = parseBech32(rawAddr); 17 | 18 | return [chainID, hrp, addr]; 19 | } 20 | 21 | export function bech32ToBytes(addrStr: string): Uint8Array { 22 | return parse(addrStr)[2]; 23 | } 24 | 25 | // format takes in a chain ID alias, bech32 HRP, and byte slice to produce a 26 | // string for an address. 27 | export function format(chainIDAlias: string, hrp: string, addr: Uint8Array) { 28 | const addrStr = formatBech32(hrp, addr); 29 | return `${chainIDAlias}${addressSep}${addrStr}`; 30 | } 31 | 32 | // parseBech32 takes a bech32 address as input and returns the HRP and data 33 | // section of a bech32 address. 34 | export function parseBech32(addrStr: string): [string, Uint8Array] { 35 | const { prefix, words } = bech32.decode(addrStr); 36 | return [prefix, bech32.fromWords(words)]; 37 | } 38 | 39 | // formatBech32 takes an address's bytes as input and returns a bech32 address. 40 | export function formatBech32(hrp: string, payload: Uint8Array) { 41 | const words = bech32.toWords(payload); 42 | return bech32.encode(hrp, words); 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/addressesFromBytes.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../serializable/fxs/common'; 2 | import { bytesCompare } from './bytesCompare'; 3 | 4 | export function addressesFromBytes(bytes: readonly Uint8Array[]): Address[] { 5 | const sortedBytes = bytes.toSorted(bytesCompare); 6 | return sortedBytes.map((b) => new Address(b)); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/avaxToNAvax.ts: -------------------------------------------------------------------------------- 1 | export const AvaxToNAvax = (num: number) => BigInt(num * 1e9); 2 | -------------------------------------------------------------------------------- /src/utils/base58.ts: -------------------------------------------------------------------------------- 1 | import { base58 } from '@scure/base'; 2 | import type { BytesCoder } from '@scure/base'; 3 | import { sha256 } from '@noble/hashes/sha256'; 4 | import { concatBytes } from './buffer'; 5 | 6 | export const base58check: BytesCoder = { 7 | encode(data) { 8 | return base58.encode(concatBytes(data, sha256(data).subarray(-4))); 9 | }, 10 | decode(string) { 11 | return base58.decode(string).subarray(0, -4); 12 | }, 13 | }; 14 | 15 | export { base58 } from '@scure/base'; 16 | -------------------------------------------------------------------------------- /src/utils/bigintMath.ts: -------------------------------------------------------------------------------- 1 | export const bigIntMin = (...args: bigint[]): bigint => 2 | args.reduce((m, e) => (e < m ? e : m)); 3 | -------------------------------------------------------------------------------- /src/utils/builderUtils.ts: -------------------------------------------------------------------------------- 1 | import { TransferableInput } from '../serializable/avax'; 2 | import type { Utxo } from '../serializable/avax/utxo'; 3 | import { matchOwners } from './addressMap'; 4 | import { addressesFromBytes } from './addressesFromBytes'; 5 | import { isTransferOut } from './typeGuards'; 6 | 7 | type GetImportedInputsFromUtxosOutput = { 8 | importedInputs: TransferableInput[]; 9 | inputUTXOs: Utxo[]; 10 | importedAmounts: Record; 11 | }; 12 | 13 | export const getImportedInputsFromUtxos = ( 14 | utxos: readonly Utxo[], 15 | fromAddressesBytes: readonly Uint8Array[], 16 | minIssuanceTime: bigint, 17 | ): GetImportedInputsFromUtxosOutput => { 18 | const fromAddresses = addressesFromBytes(fromAddressesBytes); 19 | const outputs: GetImportedInputsFromUtxosOutput = { 20 | importedInputs: [], 21 | inputUTXOs: [], 22 | importedAmounts: {}, 23 | }; 24 | 25 | return utxos.reduce((agg, utxo): GetImportedInputsFromUtxosOutput => { 26 | const { importedInputs, inputUTXOs, importedAmounts } = agg; 27 | const out = utxo.output; 28 | if (!isTransferOut(out)) return agg; 29 | 30 | const sigData = matchOwners( 31 | out.outputOwners, 32 | fromAddresses, 33 | minIssuanceTime, 34 | ); 35 | 36 | if (!sigData) return agg; 37 | 38 | importedInputs.push( 39 | TransferableInput.fromUtxoAndSigindicies(utxo, sigData.sigIndicies), 40 | ); 41 | inputUTXOs.push(utxo); 42 | importedAmounts[utxo.getAssetId()] = 43 | (importedAmounts[utxo.getAssetId()] ?? 0n) + out.amount(); 44 | return agg; 45 | }, outputs); 46 | }; 47 | -------------------------------------------------------------------------------- /src/utils/bytesCompare.ts: -------------------------------------------------------------------------------- 1 | export const bytesCompare = (a: Uint8Array, b: Uint8Array) => { 2 | let i; 3 | for (i = 0; i < a.length && i < b.length; i++) { 4 | const aByte = a[i]; 5 | const bByte = b[i]; 6 | if (aByte !== bByte) { 7 | return aByte - bByte; 8 | } 9 | } 10 | if (i === a.length && i === b.length) { 11 | // throw error? 12 | return 0; 13 | } 14 | return i === a.length ? -1 : 1; 15 | }; 16 | 17 | export const bytesEqual = (bytes1: Uint8Array, bytes2: Uint8Array): boolean => { 18 | if (bytes1.length !== bytes2.length) { 19 | return false; 20 | } 21 | return bytesCompare(bytes1, bytes2) === 0; 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/consolidate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This implementation of `consolidate` `combine`s all elements of `arr` 3 | * for which `canCombine` returns `true`. It is assumed that all combinations 4 | * can be determined with an initial scan of `arr`, and that `canCombine` is transitive, 5 | * meaning if `canCombine(A, B)` and `canCombine(B, C)` then `canCombine(combine(A, B), C)` 6 | * @param arr - an array of elements 7 | * @param canCombine - a function which determines if 2 elements can combine 8 | * @param combine - a function which combines 2 elements 9 | * @returns an array combined elements 10 | */ 11 | export const consolidate = ( 12 | arr: readonly T[], 13 | canCombine: (a: T, b: T) => boolean, 14 | combine: (a: T, b: T) => T, 15 | ): T[] => { 16 | const consolidated: T[] = []; 17 | for (const el of arr) { 18 | let combined = false; 19 | for (let i = 0; i < consolidated.length; i++) { 20 | const existing = consolidated[i]; 21 | if (canCombine(existing, el)) { 22 | consolidated[i] = combine(existing, el); 23 | combined = true; 24 | break; 25 | } 26 | } 27 | if (!combined) { 28 | consolidated.push(el); 29 | } 30 | } 31 | return consolidated; 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/costs.ts: -------------------------------------------------------------------------------- 1 | import type { UnsignedTx } from '../vms/common/unsignedTx'; 2 | 3 | export const CTxBytesGas = 1n; 4 | export const CCostPerSignature = 1000n; 5 | export const CFixedFee = 10000; 6 | 7 | export function costCorethTx(tx: UnsignedTx): bigint { 8 | const bytesCost = calcBytesCost(tx.toBytes().length); 9 | 10 | const sigCost = 11 | BigInt(tx.getSigIndices().flatMap((a) => a).length) * CCostPerSignature; 12 | const fixedFee = 10000n; 13 | return bytesCost + sigCost + fixedFee; 14 | } 15 | 16 | export function calcBytesCost(len: number): bigint { 17 | return BigInt(len) * CTxBytesGas; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/devutils.ts: -------------------------------------------------------------------------------- 1 | import util from 'util'; 2 | import { bufferToHex } from './buffer'; 3 | 4 | // useful for printing transactions 5 | export const printJSON = (obj: any) => { 6 | console.log(JSON.stringify(obj, null, 2)); 7 | }; 8 | 9 | // useful for printing nested objects 10 | export const printDeep = (obj: any) => { 11 | console.log(util.inspect(obj, { depth: null, colors: true })); 12 | }; 13 | 14 | export const printHex = (bytes: Uint8Array, name = '') => { 15 | console.log(`name = ${name}`, bufferToHex(bytes)); 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/getTransferableInputsByTx.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | avmBaseTx, 3 | importTx as avmImportTx, 4 | transferableInputs, 5 | } from '../fixtures/avax'; 6 | import { describe, it, expect } from 'vitest'; 7 | 8 | import { importTx as pvmImportTx } from '../fixtures/pvm'; 9 | import { 10 | importTx as evmImportTx, 11 | exportTx as evmExportTx, 12 | } from '../fixtures/evm'; 13 | import { getTransferableInputsByTx } from './getTransferableInputsByTx'; 14 | 15 | describe('getTransferableInputsByTx', () => { 16 | it('returns the inputs of a PVM import tx correctly', () => { 17 | expect(getTransferableInputsByTx(pvmImportTx())).toStrictEqual([ 18 | ...transferableInputs(), 19 | ...transferableInputs(), 20 | ]); 21 | }); 22 | 23 | it('returns the inputs of an AVM import tx correctly', () => { 24 | expect(getTransferableInputsByTx(avmImportTx())).toStrictEqual([ 25 | ...transferableInputs(), 26 | ...transferableInputs(), 27 | ]); 28 | }); 29 | 30 | it('returns the inputs of an EVM import tx correctly', () => { 31 | expect(getTransferableInputsByTx(evmImportTx())).toStrictEqual( 32 | transferableInputs(), 33 | ); 34 | }); 35 | 36 | it('returns the inputs of an EVM export tx correctly', () => { 37 | expect(getTransferableInputsByTx(evmExportTx())).toStrictEqual([]); 38 | }); 39 | 40 | it('returns the inputs of a non-import tx correctly', () => { 41 | expect(getTransferableInputsByTx(avmBaseTx())).toStrictEqual( 42 | transferableInputs(), 43 | ); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/utils/getTransferableInputsByTx.ts: -------------------------------------------------------------------------------- 1 | import { isImportTx as isAvmImportTx } from '../serializable/avm'; 2 | import { isImportTx as isPvmImportTx } from '../serializable/pvm'; 3 | import { 4 | isExportTx as isEvmExportTx, 5 | isImportExportTx, 6 | isEvmTx, 7 | } from '../serializable/evm'; 8 | import type { AvaxTx } from '../serializable/avax'; 9 | import type { EVMTx } from '../serializable/evm/abstractTx'; 10 | 11 | export const getTransferableInputsByEvmTx = (tx: EVMTx) => { 12 | if (isImportExportTx(tx)) { 13 | return isEvmExportTx(tx) ? [] : tx.importedInputs; 14 | } 15 | 16 | // Unreachable 17 | return []; 18 | }; 19 | 20 | export const getTransferableInputsByTx = (tx: AvaxTx | EVMTx) => { 21 | if (isEvmTx(tx)) { 22 | return getTransferableInputsByEvmTx(tx); 23 | } 24 | if (isAvmImportTx(tx) || isPvmImportTx(tx)) { 25 | return [...(tx.baseTx.inputs ?? []), ...(tx.ins ?? [])]; 26 | } 27 | 28 | return tx.getInputs(); 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/getUtxoInfo.ts: -------------------------------------------------------------------------------- 1 | import type { Utxo } from '../serializable/avax/utxo'; 2 | import { isStakeableLockOut, isTransferOut } from './typeGuards'; 3 | 4 | export type UtxoInfo = Readonly<{ 5 | /** 6 | * @default 0n 7 | */ 8 | amount: bigint; 9 | assetId: string; 10 | /** 11 | * @default 0n 12 | */ 13 | locktime: bigint; 14 | /** 15 | * @default 0n 16 | */ 17 | stakeableLocktime: bigint; 18 | /** 19 | * @default 1 20 | */ 21 | threshold: number; 22 | utxoId: string; 23 | }>; 24 | 25 | export const getUtxoInfo = (utxo: Utxo): UtxoInfo => { 26 | const { output } = utxo; 27 | const outputOwners = utxo.getOutputOwners(); 28 | 29 | return { 30 | amount: 31 | isTransferOut(output) || isStakeableLockOut(output) 32 | ? output.amount() 33 | : 0n, 34 | assetId: utxo.getAssetId(), 35 | locktime: outputOwners.locktime.value(), 36 | stakeableLocktime: isStakeableLockOut(output) 37 | ? output.getStakeableLocktime() 38 | : 0n, 39 | threshold: outputOwners.threshold.value(), 40 | utxoId: utxo.ID(), 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './address'; 2 | export * from './addressesFromBytes'; 3 | export * from './base58'; 4 | export * from './buffer'; 5 | export * from './bytesCompare'; 6 | export * from './costs'; 7 | export * from './devutils'; 8 | export * from './typeGuards'; 9 | export * from './UTXOSet'; 10 | export * from './addChecksum'; 11 | export * from './addressMap'; 12 | export * from './getTransferableInputsByTx'; 13 | export * from './getTransferableOutputsByTx'; 14 | export * from './getUtxoInfo'; 15 | export * from './getBurnedAmountByTx'; 16 | export * from './validateBurnedAmount/validateBurnedAmount'; 17 | export * from './isEtnaEnabled'; 18 | export { unpackWithManager, getManagerForVM, packTx } from './packTx'; 19 | -------------------------------------------------------------------------------- /src/utils/isEtnaEnabled.ts: -------------------------------------------------------------------------------- 1 | import type { GetUpgradesInfoResponse } from '../info/model'; 2 | 3 | export const isEtnaEnabled = ( 4 | upgradesInfo: GetUpgradesInfoResponse, 5 | ): boolean => { 6 | const { etnaTime } = upgradesInfo; 7 | return new Date(etnaTime) < new Date(); 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/models.ts: -------------------------------------------------------------------------------- 1 | export type UnpackReturn = 2 | | Uint8Array 3 | | number 4 | | bigint 5 | | string 6 | | string[] 7 | | number[]; 8 | -------------------------------------------------------------------------------- /src/utils/packTx.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_CODEC_VERSION } from '../constants/codec'; 2 | import { getAVMManager } from '../serializable/avm/codec'; 3 | import type { Manager } from '../serializable/codec'; 4 | import type { Serializable } from '../serializable/common/types'; 5 | import type { VM } from '../serializable/constants'; 6 | import { getEVMManager } from '../serializable/evm/codec'; 7 | import { Short } from '../serializable/primitives'; 8 | import { getPVMManager } from '../serializable/pvm/codec'; 9 | 10 | export interface GenericTransaction extends Serializable { 11 | vm: VM; 12 | } 13 | 14 | export function getManagerForVM(vm: VM): Manager { 15 | switch (vm) { 16 | case 'AVM': 17 | return getAVMManager(); 18 | case 'EVM': 19 | return getEVMManager(); 20 | case 'PVM': 21 | return getPVMManager(); 22 | default: 23 | throw new Error('unknown VM'); 24 | } 25 | } 26 | 27 | export function unpackWithManager(vm: VM, txBytes: Uint8Array) { 28 | return getManagerForVM(vm).unpackTransaction(txBytes); 29 | } 30 | 31 | export function packTx(tx: GenericTransaction) { 32 | return getManagerForVM(tx.vm).packCodec(tx); 33 | } 34 | 35 | export function getDefaultCodecFromTx(tx: GenericTransaction) { 36 | return getManagerForVM(tx.vm).getCodecForVersion( 37 | new Short(DEFAULT_CODEC_VERSION), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/removeDuplicateUTXOs.ts: -------------------------------------------------------------------------------- 1 | import type { Utxo } from '../serializable/avax/utxo'; 2 | 3 | /** 4 | * Returns a new array of unique UTXOs. 5 | * @param utxos 6 | */ 7 | export function filterDuplicateUTXOs(utxos: Utxo[]) { 8 | const ids = utxos.map((utxo) => utxo.ID()); 9 | return utxos.filter((utxo, index) => { 10 | return ids.indexOf(utxo.ID()) == index; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/serializeList.test.ts: -------------------------------------------------------------------------------- 1 | import { TransferableInput } from '../serializable/avax'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { transferableInputs, transferableInputsBytes } from '../fixtures/avax'; 5 | import { testCodec } from '../fixtures/codec'; 6 | import { address, addressesBytes } from '../fixtures/common'; 7 | import { Address } from '../serializable/fxs/common'; 8 | import { packList, toListStruct, unpackList } from './serializeList'; 9 | import { unpack } from './struct'; 10 | 11 | describe('SerializeList', () => { 12 | it('unpacks list', () => { 13 | const adds = addressesBytes(); 14 | expect(unpackList(adds, Address, testCodec())).toEqual([ 15 | [address(), address()], 16 | new Uint8Array([]), 17 | ]); 18 | }); 19 | 20 | it('unpacks list', () => { 21 | expect(packList([address(), address()], testCodec())).toEqual( 22 | addressesBytes(), 23 | ); 24 | }); 25 | 26 | it('unpack for list type', () => { 27 | const transferableInputsType = toListStruct(TransferableInput); 28 | const [result, remaining] = unpack( 29 | transferableInputsBytes(), 30 | [transferableInputsType], 31 | testCodec(), 32 | ); 33 | expect(result).toEqual(transferableInputs()); 34 | expect(remaining).toHaveLength(0); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/sort.ts: -------------------------------------------------------------------------------- 1 | import { getPVMManager } from '../serializable/pvm/codec'; 2 | import type { TransferableOutput } from '../serializable/avax'; 3 | import { getAVMManager } from '../serializable/avm/codec'; 4 | import type { Output } from '../serializable/evm'; 5 | import { Id } from '../serializable/fxs/common'; 6 | import { bytesCompare } from './bytesCompare'; 7 | import { isStakeableLockOut } from './typeGuards'; 8 | 9 | export const compareTransferableOutputs = ( 10 | output1: TransferableOutput, 11 | output2: TransferableOutput, 12 | ): number => { 13 | const assetIdRes = Id.compare(output1.assetId, output2.assetId); 14 | if (assetIdRes !== 0) { 15 | return assetIdRes; 16 | } 17 | 18 | const pvmOutputTypes = [isStakeableLockOut]; 19 | const avmCodec = getAVMManager().getDefaultCodec(); 20 | const pvmCodec = getPVMManager().getDefaultCodec(); 21 | 22 | const codec1 = pvmOutputTypes.some((checker) => checker(output1.output)) 23 | ? pvmCodec 24 | : avmCodec; 25 | const codec2 = pvmOutputTypes.some((checker) => checker(output2.output)) 26 | ? pvmCodec 27 | : avmCodec; 28 | 29 | return bytesCompare(output1.toBytes(codec1), output2.toBytes(codec2)); 30 | }; 31 | 32 | export const compareEVMOutputs = (a: Output, b: Output) => { 33 | if (a.address.value() === b.address.value()) { 34 | return bytesCompare(a.assetId.toBytes(), b.assetId.toBytes()); 35 | } 36 | return a.address.value().localeCompare(b.address.value()); 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/struct.test.ts: -------------------------------------------------------------------------------- 1 | import { TransferableOutput } from '../serializable/avax'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { transferableOutput, transferableOutputBytes } from '../fixtures/avax'; 5 | import { testCodec } from '../fixtures/codec'; 6 | import { 7 | mintOutput, 8 | mintOutputBytes, 9 | transferOutput, 10 | transferOutputBytes, 11 | } from '../fixtures/secp256k1'; 12 | import { MintOutput, TransferOutput } from '../serializable/fxs/secp256k1'; 13 | import { concatBytes } from './buffer'; 14 | import { unpack } from './struct'; 15 | 16 | describe('structSimple', () => { 17 | it('unpackSimple', () => { 18 | const input: Uint8Array = concatBytes( 19 | transferOutputBytes(), 20 | mintOutputBytes(), 21 | ); 22 | 23 | const outputArray = [TransferOutput, MintOutput] as const; 24 | const [tsOutput, mntOutput, remaining] = unpack(input, outputArray); 25 | expect(tsOutput).toEqual(transferOutput()); 26 | expect(mntOutput).toEqual(mintOutput()); 27 | expect(remaining).toEqual(new Uint8Array()); 28 | }); 29 | 30 | it('unpackSimple with codec', () => { 31 | const input: Uint8Array = concatBytes( 32 | transferableOutputBytes(), 33 | mintOutputBytes(), 34 | ); 35 | 36 | const outputArray = [TransferableOutput, MintOutput] as const; 37 | const [tsOutput, mntOutput, remaining] = unpack( 38 | input, 39 | outputArray, 40 | testCodec(), 41 | ); 42 | expect(tsOutput).toEqual(transferableOutput()); 43 | expect(mntOutput).toEqual(mintOutput()); 44 | expect(remaining).toEqual(new Uint8Array()); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/utils/transferableAmounts.ts: -------------------------------------------------------------------------------- 1 | import type { Amounter } from '../serializable/common/types'; 2 | 3 | export interface TransferableSummable extends Amounter { 4 | getAssetId(): string; 5 | } 6 | 7 | export const transferableAmounts = (transferables: TransferableSummable[]) => { 8 | return transferables.reduce((agg, transferable) => { 9 | agg[transferable.getAssetId()] = agg[transferable.getAssetId()] ?? 0n; 10 | agg[transferable.getAssetId()] += transferable.amount(); 11 | return agg; 12 | }, {} as Record); 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/typeGuards.ts: -------------------------------------------------------------------------------- 1 | import type { Serializable } from '../serializable/common/types'; 2 | import type { TransferInput } from '../serializable/fxs/secp256k1'; 3 | import type { OutputOwners } from '../serializable/fxs/secp256k1/outputOwners'; 4 | import type { TransferOutput } from '../serializable/fxs/secp256k1/transferOutput'; 5 | import type { StakeableLockIn } from '../serializable/pvm/stakeableLockIn'; 6 | import type { StakeableLockOut } from '../serializable/pvm/stakeableLockOut'; 7 | import { TypeSymbols } from '../serializable/constants'; 8 | 9 | export function isTransferOut(out: Serializable): out is TransferOutput { 10 | return out._type === TypeSymbols.TransferOutput; 11 | } 12 | 13 | export function isStakeableLockOut(out: Serializable): out is StakeableLockOut { 14 | return out._type === TypeSymbols.StakeableLockOut; 15 | } 16 | 17 | export function isRewardsOwner(out: Serializable): out is OutputOwners { 18 | return out._type === TypeSymbols.OutputOwners; 19 | } 20 | 21 | export function isStakeableLockIn(out: Serializable): out is StakeableLockIn { 22 | return out._type === TypeSymbols.StakeableLockIn; 23 | } 24 | 25 | export function isTransferInput(inp: Serializable): inp is TransferInput { 26 | return inp._type === TypeSymbols.TransferInput; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/validateBurnedAmount/validateDynamicBurnedAmount.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Validate dynamic burned amount for avalanche c/p transactions 3 | * 4 | * @param burnedAmount: burned amount in nAVAX 5 | * @param feeAmount: fee 6 | * @param feeTolerance: tolerance percentage range where the burned amount is considered valid. e.g.: with FeeTolerance = 20% -> (expectedFee <= burnedAmount <= expectedFee * 1.2) 7 | * @return {boolean} isValid: : true if the burned amount is valid, false otherwise. 8 | * @return {bigint} txFee: burned amount in nAVAX 9 | */ 10 | export const validateDynamicBurnedAmount = ({ 11 | burnedAmount, 12 | feeAmount, 13 | feeTolerance, 14 | }: { 15 | burnedAmount: bigint; 16 | feeAmount: bigint; 17 | feeTolerance: number; 18 | }): { isValid: boolean; txFee: bigint } => { 19 | const feeToleranceInt = Math.floor(feeTolerance); 20 | 21 | if (feeToleranceInt < 1 || feeToleranceInt > 100) { 22 | throw new Error('feeTolerance must be [1,100]'); 23 | } 24 | 25 | const min = (feeAmount * (100n - BigInt(feeToleranceInt))) / 100n; 26 | const max = (feeAmount * (100n + BigInt(feeToleranceInt))) / 100n; 27 | 28 | return { 29 | isValid: burnedAmount >= min && burnedAmount <= max, 30 | txFee: burnedAmount, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/validateBurnedAmount/validateStaticBurnedAmount.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '../../vms/context/model'; 2 | import type { UnsignedTx } from '../../vms/common'; 3 | import { 4 | isAvmBaseTx, 5 | isExportTx as isAvmExportTx, 6 | isImportTx as isAvmImportTx, 7 | } from '../../serializable/avm'; 8 | 9 | /** 10 | * Validate static burned amount for avalanche x transactions 11 | * 12 | * @param unsignedTx: unsigned transaction 13 | * @param context 14 | * @param burnedAmount: burned amount in nAVAX 15 | * @return {boolean} isValid: : true if the burned amount is valid, false otherwise. 16 | * @return {bigint} txFee: burned amount in nAVAX 17 | */ 18 | export const validateStaticBurnedAmount = ({ 19 | unsignedTx, 20 | context, 21 | burnedAmount, 22 | }: { 23 | unsignedTx: UnsignedTx; 24 | context: Context; 25 | burnedAmount: bigint; 26 | }): { isValid: boolean; txFee: bigint } => { 27 | const tx = unsignedTx.getTx(); 28 | 29 | if (isAvmBaseTx(tx) || isAvmExportTx(tx) || isAvmImportTx(tx)) { 30 | return validate(burnedAmount, context.baseTxFee); 31 | } 32 | 33 | throw new Error('tx type is not supported'); 34 | }; 35 | 36 | const validate = (burnedAmount: bigint, expectedAmount: bigint) => ({ 37 | isValid: burnedAmount === expectedAmount, 38 | txFee: expectedAmount, 39 | }); 40 | -------------------------------------------------------------------------------- /src/vms/avm/api.ts: -------------------------------------------------------------------------------- 1 | import { getAVMManager } from '../../serializable/avm/codec'; 2 | import { AvaxApi } from '../common/avaxApi'; 3 | import type { 4 | BuildGenesisParams, 5 | BuildGenesisResponse, 6 | GetAllBalancesParams, 7 | GetAllBalancesResponse, 8 | GetTxFeeResponse, 9 | TxFee, 10 | } from './models'; 11 | 12 | export class AVMApi extends AvaxApi { 13 | constructor(baseURL?: string) { 14 | super(baseURL, '/ext/bc/X', 'avm', getAVMManager()); 15 | } 16 | 17 | /** 18 | * Given a JSON representation of this Virtual Machine’s genesis state, create the byte representation of that state. 19 | * 20 | * @param genesisData The blockchain's genesis data object 21 | * 22 | * @returns Promise of a string of bytes 23 | */ 24 | buildGenesis = async ( 25 | params: BuildGenesisParams, 26 | ): Promise => { 27 | return await this.callRpc('buildGenesis', params); 28 | }; 29 | 30 | getAllBalances( 31 | getAllBalancesParams: GetAllBalancesParams, 32 | ): Promise { 33 | return this.callRpc( 34 | 'getAllBalances', 35 | getAllBalancesParams, 36 | ); 37 | } 38 | 39 | getTxFee = async (): Promise => { 40 | const txFee = await this.callRpc('getTxFee'); 41 | return { 42 | txFee: BigInt(txFee.txFee), 43 | createAssetTxFee: BigInt(txFee.createAssetTxFee), 44 | }; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/vms/avm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './builder'; 3 | export * from './models'; 4 | -------------------------------------------------------------------------------- /src/vms/avm/models.ts: -------------------------------------------------------------------------------- 1 | export interface BuildGenesisParams { 2 | genesisData: object; 3 | } 4 | 5 | export interface BuildGenesisResponse { 6 | bytes: string; 7 | encoding: string; 8 | } 9 | 10 | export interface GetAllBalancesParams { 11 | address: string; 12 | } 13 | 14 | export interface GetAllBalancesResponse { 15 | balances: { asset: string; balance: number }[]; 16 | } 17 | 18 | export interface GetBalanceParams { 19 | address: string; 20 | assetID: string; 21 | } 22 | 23 | export interface GetBalanceResponse { 24 | balance: number; 25 | } 26 | 27 | export interface GetAddressTxsParams { 28 | address: string; 29 | cursor?: bigint; 30 | assetID: string; 31 | pageSize?: bigint; 32 | } 33 | 34 | export interface GetAddressTxsResponse { 35 | txIDs: string[]; 36 | cursor: bigint; 37 | } 38 | 39 | export interface GetTxFeeResponse { 40 | txFee: string; 41 | createAssetTxFee: string; 42 | } 43 | 44 | export interface TxFee { 45 | txFee: bigint; 46 | createAssetTxFee: bigint; 47 | } 48 | -------------------------------------------------------------------------------- /src/vms/avm/utxoCalculationFns/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAvmAndCorethUTXOs'; 2 | -------------------------------------------------------------------------------- /src/vms/common/apiModels.ts: -------------------------------------------------------------------------------- 1 | import type { Utxo } from '../../serializable/avax/utxo'; 2 | 3 | export type GetUTXOsInput = { 4 | addresses: string[]; 5 | limit?: number; 6 | startIndex?: { 7 | address: string; 8 | utxo: string; 9 | }; 10 | sourceChain?: string; 11 | }; 12 | 13 | export type GetUTXOsApiResp = { 14 | numFetched: number; 15 | utxos: string[]; 16 | endIndex: { 17 | address: string; 18 | utxo: string; 19 | }; 20 | sourceChain?: string; 21 | encoding: string; 22 | }; 23 | 24 | export type GetUTXOResponse = Omit & { 25 | utxos: Utxo[]; 26 | }; 27 | 28 | export type GetAssetDescriptionResponse = { 29 | assetID: string; 30 | name: string; 31 | symbol: string; 32 | denomination: number; 33 | }; 34 | 35 | export type AVMContext = { 36 | readonly networkID: number; 37 | readonly hrp: string; 38 | readonly blockchainID: string; 39 | readonly avaxAssetID: string; 40 | readonly baseTxFee: bigint; 41 | readonly createAssetTxFee: bigint; 42 | }; 43 | 44 | export interface IssueTxParams { 45 | tx: string; 46 | } 47 | 48 | export interface IssueTxResponse { 49 | txID: string; 50 | } 51 | 52 | export interface GetTxParams { 53 | txID: string; 54 | } 55 | 56 | export interface GetTxStatusParams { 57 | txID: string; 58 | includeReason?: boolean | true; 59 | } 60 | 61 | export interface GetTxStatusResponse { 62 | status: string; 63 | reason: string; 64 | } 65 | -------------------------------------------------------------------------------- /src/vms/common/baseApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | this class has methods that pertain to all api sections 3 | */ 4 | 5 | import { AVAX_PUBLIC_URL } from '../../constants/public-urls'; 6 | import { JrpcProvider } from './rpc'; 7 | 8 | export abstract class Api { 9 | protected rpcProvider: JrpcProvider; 10 | 11 | constructor( 12 | baseURL: string = AVAX_PUBLIC_URL, 13 | protected path: string, 14 | protected base?: string, 15 | protected fetchOptions?: RequestInit, 16 | ) { 17 | this.rpcProvider = new JrpcProvider(baseURL + path); 18 | } 19 | 20 | setFetchOptions(options: RequestInit | undefined) { 21 | this.fetchOptions = options; 22 | } 23 | 24 | protected getMethodName = (methodName: string) => { 25 | if (!this.base) { 26 | return methodName; 27 | } 28 | return `${this.base}.${methodName}`; 29 | }; 30 | 31 | protected callRpc = ( 32 | methodName: string, 33 | params?: Array> | Record, 34 | ): Promise => 35 | this.rpcProvider.callMethod( 36 | this.getMethodName(methodName), 37 | params, 38 | this.fetchOptions, 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/vms/common/builder.ts: -------------------------------------------------------------------------------- 1 | import type { TransferableInput, TransferableOutput } from '../../serializable'; 2 | import type { Context } from '../context/model'; 3 | import { BaseTx as AvaxBaseTx } from '../../serializable/avax'; 4 | 5 | /** 6 | * format a BaseTx for xChain directly from inputs with no validation 7 | * @param changeOutputs - the output representing the remaining amounts from each input 8 | * @param inputs - the inputs of the tx 9 | * @param memo - optional memo 10 | */ 11 | export const baseTxUnsafeAvm = ( 12 | context: Context, 13 | changeOutputs: TransferableOutput[], 14 | inputs: TransferableInput[], 15 | memo: Uint8Array, 16 | ) => { 17 | return AvaxBaseTx.fromNative( 18 | context.networkID, 19 | context.xBlockchainID, 20 | changeOutputs, 21 | inputs, 22 | memo, 23 | ); 24 | }; 25 | 26 | /** 27 | * format a BaseTx for pChain directly from inputs with no validation 28 | * @param changeOutputs - the output representing the remaining amounts from each input 29 | * @param inputs - the inputs of the tx 30 | * @param memo - optional memo 31 | */ 32 | export const baseTxUnsafePvm = ( 33 | context: Context, 34 | changeOutputs: readonly TransferableOutput[], 35 | inputs: readonly TransferableInput[], 36 | memo: Uint8Array, 37 | ) => { 38 | return AvaxBaseTx.fromNative( 39 | context.networkID, 40 | context.pBlockchainID, 41 | changeOutputs, 42 | inputs, 43 | memo, 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/vms/common/chainAPI.ts: -------------------------------------------------------------------------------- 1 | /* 2 | this class includes methods that are shared between all three chains/vms 3 | */ 4 | 5 | import { AVAX_PUBLIC_URL } from '../../constants/public-urls'; 6 | import type { SignedTx } from '../../serializable/avax'; 7 | import { Utxo } from '../../serializable/avax/utxo'; 8 | import type { Manager } from '../../serializable/codec'; 9 | import { addChecksum } from '../../utils'; 10 | import { bufferToHex, hexToBuffer } from '../../utils/buffer'; 11 | import type { 12 | GetUTXOsApiResp, 13 | GetUTXOsInput, 14 | IssueTxParams, 15 | IssueTxResponse, 16 | } from './apiModels'; 17 | import { Api } from './baseApi'; 18 | 19 | export abstract class ChainApi extends Api { 20 | constructor( 21 | baseURL: string = AVAX_PUBLIC_URL, 22 | protected path: string, 23 | protected base: string, 24 | protected manager: Manager, 25 | ) { 26 | super(baseURL, path, base); 27 | } 28 | 29 | async getUTXOs(input: GetUTXOsInput) { 30 | const resp = await this.callRpc('getUTXOs', { 31 | ...input, 32 | encoding: 'hex', 33 | }); 34 | const utxos = resp.utxos.map((utxoHex) => 35 | this.manager.unpack(hexToBuffer(utxoHex), Utxo), 36 | ); 37 | 38 | return { 39 | ...resp, 40 | utxos, 41 | }; 42 | } 43 | 44 | issueTx(issueTxParams: IssueTxParams): Promise { 45 | return this.callRpc('issueTx', issueTxParams); 46 | } 47 | 48 | issueSignedTx(tx: SignedTx): Promise { 49 | return this.issueTx({ 50 | tx: bufferToHex(addChecksum(tx.toBytes())), 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/vms/common/defaultSpendOptions.ts: -------------------------------------------------------------------------------- 1 | import type { SpendOptions, SpendOptionsRequired } from './models'; 2 | 3 | export const defaultSpendOptions = ( 4 | fromAddress: readonly Uint8Array[], 5 | options?: SpendOptions, 6 | ): SpendOptionsRequired => { 7 | return { 8 | minIssuanceTime: BigInt(Math.floor(new Date().getTime() / 1000)), 9 | changeAddresses: fromAddress, 10 | threshold: 1, 11 | memo: new Uint8Array(), 12 | locktime: 0n, 13 | // Only include options that are not undefined 14 | ...Object.fromEntries( 15 | Object.entries(options || {}).filter(([, v]) => v !== undefined), 16 | ), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/vms/common/evmUnsignedTx.ts: -------------------------------------------------------------------------------- 1 | import { secp256k1 } from '../../crypto'; 2 | import { UnsignedTx } from './unsignedTx'; 3 | import { Address } from '../../serializable/fxs/common'; 4 | 5 | export class EVMUnsignedTx extends UnsignedTx { 6 | hasPubkey(pubKey: Uint8Array): boolean { 7 | const addrAvax = new Address(this.publicKeyBytesToAddress(pubKey)); 8 | const addrEVM = new Address(secp256k1.publicKeyToEthAddress(pubKey)); 9 | 10 | return this.hasAddress(addrAvax) || this.hasAddress(addrEVM); 11 | } 12 | 13 | static fromJSON(jsonStr: string) { 14 | const tx = UnsignedTx.fromJSON(jsonStr); 15 | return new EVMUnsignedTx(tx.tx, tx.utxos, tx.addressMaps, tx.credentials); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/vms/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './apiModels'; 2 | export * from './builder'; 3 | export * from './models'; 4 | export { AvaxApi } from './avaxApi'; 5 | export { UnsignedTx } from './unsignedTx'; 6 | export { Transaction } from './transaction'; 7 | export * from './fees/dimensions'; 8 | -------------------------------------------------------------------------------- /src/vms/common/models.ts: -------------------------------------------------------------------------------- 1 | export type SpendOptions = { 2 | minIssuanceTime?: bigint; 3 | changeAddresses?: readonly Uint8Array[]; 4 | threshold?: number; 5 | memo?: Uint8Array; 6 | locktime?: bigint; 7 | }; 8 | 9 | export type SpendOptionsRequired = Required; 10 | 11 | //the string is address in hex 12 | export type SigMapping = Map; 13 | export type SigMappings = SigMapping[]; 14 | -------------------------------------------------------------------------------- /src/vms/common/rpc.ts: -------------------------------------------------------------------------------- 1 | export type RpcCallOptions = { 2 | headers?: Record; 3 | }; 4 | 5 | type JsonRpcSuccessResp = { 6 | jsonrpc: string; 7 | result: T; 8 | id: number; 9 | error?: undefined; 10 | }; 11 | 12 | interface JsonRpcError { 13 | code: number; 14 | message: string; 15 | data?: any; 16 | } 17 | 18 | type JsonRpcErrorResp = { 19 | jsonrpc: string; 20 | id: number; 21 | result?: undefined; 22 | error: JsonRpcError; 23 | }; 24 | 25 | export class JrpcProvider { 26 | private reqId = 0; 27 | 28 | constructor(private readonly url: string) {} 29 | 30 | async callMethod( 31 | method: string, 32 | parameters?: Array> | Record, 33 | fetchOptions?: RequestInit, 34 | ): Promise { 35 | const body = { 36 | jsonrpc: '2.0', 37 | id: this.reqId++, 38 | method, 39 | params: parameters, 40 | }; 41 | const resp = await fetch(this.url, { 42 | ...fetchOptions, 43 | method: 'POST', 44 | body: JSON.stringify(body), 45 | headers: { 46 | 'Content-Type': 'application/json', 47 | ...fetchOptions?.headers, 48 | }, 49 | }) 50 | .then(async (r) => { 51 | return r.json(); 52 | }) 53 | .then((data) => data as JsonRpcSuccessResp | JsonRpcErrorResp); 54 | 55 | if (resp.error) throw new Error(resp.error.message); 56 | 57 | return resp.result; 58 | } 59 | 60 | // TODO: Batch RPC call 61 | } 62 | -------------------------------------------------------------------------------- /src/vms/common/transaction.ts: -------------------------------------------------------------------------------- 1 | import type { Codec } from '../../serializable/codec'; 2 | import type { VM, TypeSymbols } from '../../serializable/constants'; 3 | 4 | export abstract class Transaction { 5 | abstract _type: TypeSymbols; 6 | abstract toBytes(codec: Codec): Uint8Array; 7 | abstract vm: VM; 8 | abstract getBlockchainId(): string; 9 | 10 | abstract getSigIndices(): number[][]; 11 | 12 | getVM() { 13 | return this.vm; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/vms/common/types.ts: -------------------------------------------------------------------------------- 1 | import type { Codec } from '../../serializable/codec'; 2 | import type { TypeSymbols } from '../../serializable/constants'; 3 | 4 | export interface Serializable { 5 | _type: TypeSymbols; 6 | 7 | toBytes(codec: Codec): Uint8Array; 8 | } 9 | 10 | export interface SerializableStatic { 11 | new (...args: any[]): Serializable; 12 | 13 | fromBytes(bytes: Uint8Array, codec: Codec): [Serializable, Uint8Array]; 14 | } 15 | 16 | export function staticImplements() { 17 | return (constructor: U) => { 18 | constructor; 19 | }; 20 | } 21 | 22 | export function serializable() { 23 | return staticImplements(); 24 | } 25 | 26 | export interface Amounter extends Serializable { 27 | amount(): bigint; 28 | } 29 | -------------------------------------------------------------------------------- /src/vms/context/context.ts: -------------------------------------------------------------------------------- 1 | import { InfoApi } from '../../info'; 2 | import { getHRP } from '../../constants/networkIDs'; 3 | import { AVMApi } from '../avm/api'; 4 | import { PVMApi } from '../pvm'; 5 | import type { Context } from './model'; 6 | 7 | /* 8 | grabs some basic info about an avm chain 9 | */ 10 | export const getContextFromURI = async ( 11 | baseURL?: string, 12 | assetDescription = 'AVAX', 13 | ): Promise => { 14 | const pChainApi = new PVMApi(baseURL); 15 | const xChainApi = new AVMApi(baseURL); 16 | const { assetID: avaxAssetID } = await xChainApi.getAssetDescription( 17 | assetDescription, 18 | ); 19 | const info = new InfoApi(baseURL); 20 | const { txFee, createAssetTxFee } = await xChainApi.getTxFee(); 21 | 22 | const { blockchainID: xBlockchainID } = await info.getBlockchainId('X'); 23 | const { blockchainID: pBlockchainID } = await info.getBlockchainId('P'); 24 | const { blockchainID: cBlockchainID } = await info.getBlockchainId('C'); 25 | 26 | const { networkID: networkIDstring } = await info.getNetworkId(); 27 | const networkID = Number(networkIDstring); 28 | 29 | const platformFeeConfig = await pChainApi.getFeeConfig(); 30 | 31 | return Object.freeze({ 32 | xBlockchainID, 33 | pBlockchainID, 34 | cBlockchainID, 35 | avaxAssetID, 36 | baseTxFee: txFee, 37 | createAssetTxFee: createAssetTxFee, 38 | networkID, 39 | hrp: getHRP(networkID), 40 | platformFeeConfig, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/vms/context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context'; 2 | export * from './model'; 3 | -------------------------------------------------------------------------------- /src/vms/context/model.ts: -------------------------------------------------------------------------------- 1 | import type { FeeConfig } from '../pvm'; 2 | 3 | export type Context = { 4 | readonly networkID: number; 5 | readonly hrp: string; 6 | readonly xBlockchainID: string; 7 | readonly pBlockchainID: string; 8 | readonly cBlockchainID: string; 9 | readonly avaxAssetID: string; 10 | readonly baseTxFee: bigint; 11 | readonly createAssetTxFee: bigint; 12 | 13 | // Post Etna 14 | readonly platformFeeConfig: FeeConfig; 15 | }; 16 | -------------------------------------------------------------------------------- /src/vms/evm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './builder'; 3 | export * from './model'; 4 | -------------------------------------------------------------------------------- /src/vms/evm/model.ts: -------------------------------------------------------------------------------- 1 | export interface GetAtomicTxParams { 2 | txID: string; 3 | } 4 | 5 | export interface GetAtomicTxStatusResponse { 6 | status: string; 7 | blockHeight: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/vms/evm/privateModels.ts: -------------------------------------------------------------------------------- 1 | export interface GetAtomicTxServerResponse { 2 | tx: string; 3 | encoding: string; 4 | blockHeight: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/vms/index.ts: -------------------------------------------------------------------------------- 1 | export * as avm from './avm'; 2 | export * as Common from './common'; 3 | export { EVMUnsignedTx } from './common/evmUnsignedTx'; 4 | export { UnsignedTx } from './common/unsignedTx'; 5 | export * as Context from './context'; 6 | export * as evm from './evm'; 7 | export * as pvm from './pvm'; 8 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/index.ts: -------------------------------------------------------------------------------- 1 | export * from './builder'; 2 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/errors.ts: -------------------------------------------------------------------------------- 1 | export const IncorrectStakeableLockOutError = new Error( 2 | 'StakeableLockOut transferOut must be a TransferOutput.', 3 | ); 4 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/index.ts: -------------------------------------------------------------------------------- 1 | export { handleFeeAndChange } from './handleFeeAndChange'; 2 | export { useSpendableLockedUTXOs } from './useSpendableLockedUTXOs'; 3 | export { useUnlockedUTXOs } from './useUnlockedUTXOs'; 4 | export { verifyAssetsConsumed } from './verifyAssetsConsumed'; 5 | export { verifyGasUsage } from './verifyGasUsage'; 6 | 7 | export type * from './types'; 8 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/types.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '../../../context'; 2 | import type { SpendProps } from '../spend'; 3 | import type { SpendHelper } from '../spendHelper'; 4 | 5 | export type SpendReducerState = Readonly< 6 | Required> 7 | >; 8 | 9 | export type SpendReducerFunction = ( 10 | state: SpendReducerState, 11 | spendHelper: SpendHelper, 12 | context: Context, 13 | ) => SpendReducerState; 14 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/verifyAssetsConsumed.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from 'vitest'; 2 | import { testContext } from '../../../../fixtures/context'; 3 | import { getInitialReducerState, getSpendHelper } from './fixtures/reducers'; 4 | import { verifyAssetsConsumed } from './verifyAssetsConsumed'; 5 | 6 | describe('verifyAssetsConsumed', () => { 7 | test('returns original state if all assets are consumed', () => { 8 | const initialState = getInitialReducerState(); 9 | const spendHelper = getSpendHelper(); 10 | const spy = vi.spyOn(spendHelper, 'verifyAssetsConsumed'); 11 | 12 | const state = verifyAssetsConsumed(initialState, spendHelper, testContext); 13 | 14 | expect(state).toBe(initialState); 15 | expect(spy).toHaveBeenCalledTimes(1); 16 | }); 17 | 18 | test('throws an error if some assets are not consumed', () => { 19 | const initialState = getInitialReducerState(); 20 | const spendHelper = getSpendHelper(); 21 | 22 | // Mock the verifyAssetsConsumed method to throw an error 23 | // Testing for this function can be found in the spendHelper.test.ts file 24 | spendHelper.verifyAssetsConsumed = vi.fn(() => { 25 | return new Error('Test error'); 26 | }); 27 | 28 | expect(() => 29 | verifyAssetsConsumed(initialState, spendHelper, testContext), 30 | ).toThrow('Test error'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/verifyAssetsConsumed.ts: -------------------------------------------------------------------------------- 1 | import type { SpendReducerFunction } from './types'; 2 | 3 | /** 4 | * Verify that all assets have been consumed. 5 | * 6 | * Calls the spendHelper's verifyAssetsConsumed method. 7 | */ 8 | export const verifyAssetsConsumed: SpendReducerFunction = ( 9 | state, 10 | spendHelper, 11 | ) => { 12 | const verifyError = spendHelper.verifyAssetsConsumed(); 13 | 14 | if (verifyError) { 15 | throw verifyError; 16 | } 17 | 18 | return state; 19 | }; 20 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/verifyGasUsage.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from 'vitest'; 2 | import { testContext } from '../../../../fixtures/context'; 3 | import { getInitialReducerState, getSpendHelper } from './fixtures/reducers'; 4 | import { verifyGasUsage } from './verifyGasUsage'; 5 | 6 | describe('verifyGasUsage', () => { 7 | test('returns original state if gas is under the threshold', () => { 8 | const initialState = getInitialReducerState(); 9 | const spendHelper = getSpendHelper(); 10 | const spy = vi.spyOn(spendHelper, 'verifyGasUsage'); 11 | 12 | const state = verifyGasUsage(initialState, spendHelper, testContext); 13 | 14 | expect(state).toBe(initialState); 15 | expect(spy).toHaveBeenCalledTimes(1); 16 | }); 17 | 18 | test('throws an error if gas is over the threshold', () => { 19 | const initialState = getInitialReducerState(); 20 | const spendHelper = getSpendHelper(); 21 | 22 | // Mock the verifyGasUsage method to throw an error 23 | // Testing for this function can be found in the spendHelper.test.ts file 24 | spendHelper.verifyGasUsage = vi.fn(() => { 25 | return new Error('Test error'); 26 | }); 27 | 28 | expect(() => 29 | verifyGasUsage(initialState, spendHelper, testContext), 30 | ).toThrow('Test error'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/vms/pvm/etna-builder/spend-reducers/verifyGasUsage.ts: -------------------------------------------------------------------------------- 1 | import type { SpendReducerFunction } from './types'; 2 | 3 | /** 4 | * Verify that gas usage is within limits. 5 | * 6 | * Calls the spendHelper's verifyGasUsage method. 7 | */ 8 | export const verifyGasUsage: SpendReducerFunction = (state, spendHelper) => { 9 | const verifyError = spendHelper.verifyGasUsage(); 10 | 11 | if (verifyError) { 12 | throw verifyError; 13 | } 14 | 15 | return state; 16 | }; 17 | -------------------------------------------------------------------------------- /src/vms/pvm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './etna-builder'; 2 | export * from './models'; 3 | export * from './api'; 4 | export * from './txs/fee'; 5 | 6 | /** 7 | * @deprecated PVM builder functions aliased under "e" are deprecated. Please use the builder functions on the root pvm export. Ex: pvm.e.newBaseTx -> pvm.newBaseTx. 8 | */ 9 | export * as e from './etna-builder'; 10 | -------------------------------------------------------------------------------- /src/vms/pvm/privateModels.ts: -------------------------------------------------------------------------------- 1 | /* 2 | models in this file are intermediate and not meant to be exported externally 3 | */ 4 | 5 | export interface GetRewardUTXOsServerResponse { 6 | numFetched: number; 7 | utxos: string[]; 8 | } 9 | 10 | export interface GetStakeServerResponse { 11 | staked: bigint; 12 | stakedOutputs: string[]; 13 | } 14 | 15 | export interface GetTxServerResponse { 16 | tx: any; 17 | } 18 | -------------------------------------------------------------------------------- /src/vms/pvm/txs/fee/calculator.test.ts: -------------------------------------------------------------------------------- 1 | import { txHexToTransaction } from '../../../../fixtures/transactions'; 2 | import { describe, test, expect } from 'vitest'; 3 | 4 | import { calculateFee } from './calculator'; 5 | import { 6 | TEST_DYNAMIC_PRICE, 7 | TEST_DYNAMIC_WEIGHTS, 8 | TEST_TRANSACTIONS, 9 | TEST_UNSUPPORTED_TRANSACTIONS, 10 | } from './fixtures/transactions'; 11 | 12 | describe('Calculator', () => { 13 | describe('calculateFee', () => { 14 | test.each(TEST_TRANSACTIONS)( 15 | 'calculates the fee for $name', 16 | ({ txHex, expectedDynamicFee }) => { 17 | const result = calculateFee( 18 | txHexToTransaction('PVM', txHex), 19 | TEST_DYNAMIC_WEIGHTS, 20 | TEST_DYNAMIC_PRICE, 21 | ); 22 | 23 | expect(result).toBe(expectedDynamicFee); 24 | }, 25 | ); 26 | 27 | test.each(TEST_UNSUPPORTED_TRANSACTIONS)( 28 | 'unsupported tx - $name', 29 | ({ txHex }) => { 30 | const tx = txHexToTransaction('PVM', txHex); 31 | 32 | expect(() => { 33 | calculateFee(tx, TEST_DYNAMIC_WEIGHTS, TEST_DYNAMIC_PRICE); 34 | }).toThrow('Unsupported transaction type.'); 35 | }, 36 | ); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/vms/pvm/txs/fee/calculator.ts: -------------------------------------------------------------------------------- 1 | import type { Transaction } from '../../../common'; 2 | import type { Dimensions } from '../../../common/fees/dimensions'; 3 | import { dimensionsToGas } from '../../../common/fees/dimensions'; 4 | import { getTxComplexity } from './complexity'; 5 | 6 | /** 7 | * Calculates the minimum required fee, in nAVAX, that an unsigned 8 | * transaction must pay for valid inclusion into a block. 9 | */ 10 | export const calculateFee = ( 11 | tx: Transaction, 12 | weights: Dimensions, 13 | price: bigint, 14 | ): bigint => { 15 | const complexity = getTxComplexity(tx); 16 | const gas = dimensionsToGas(complexity, weights); 17 | 18 | return gas * price; 19 | }; 20 | -------------------------------------------------------------------------------- /src/vms/pvm/txs/fee/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | INTRINSIC_ADD_PERMISSIONLESS_DELEGATOR_TX_COMPLEXITIES, 3 | INTRINSIC_ADD_PERMISSIONLESS_VALIDATOR_TX_COMPLEXITIES, 4 | INTRINSIC_ADD_SUBNET_VALIDATOR_TX_COMPLEXITIES, 5 | INTRINSIC_BASE_TX_COMPLEXITIES, 6 | INTRINSIC_CREATE_CHAIN_TX_COMPLEXITIES, 7 | INTRINSIC_CREATE_SUBNET_TX_COMPLEXITIES, 8 | INTRINSIC_EXPORT_TX_COMPLEXITIES, 9 | INTRINSIC_IMPORT_TX_COMPLEXITIES, 10 | INTRINSIC_REMOVE_SUBNET_VALIDATOR_TX_COMPLEXITIES, 11 | INTRINSIC_TRANSFER_SUBNET_OWNERSHIP_TX_COMPLEXITIES, 12 | INTRINSIC_CONVERT_SUBNET_TO_L1_TX_COMPLEXITIES, 13 | INTRINSIC_DISABLE_L1_VALIDATOR_TX_COMPLEXITIES, 14 | INTRINSIC_INCREASE_L1_VALIDATOR_BALANCE_TX_COMPLEXITIES, 15 | INTRINSIC_REGISTER_L1_VALIDATOR_TX_COMPLEXITIES, 16 | INTRINSIC_SET_L1_VALIDATOR_WEIGHT_TX_COMPLEXITIES, 17 | } from './constants'; 18 | 19 | export { 20 | getAuthComplexity, 21 | getInputComplexity, 22 | getOutputComplexity, 23 | getOwnerComplexity, 24 | getSignerComplexity, 25 | getTxComplexity, 26 | getBytesComplexity, 27 | getL1ValidatorsComplexity, 28 | } from './complexity'; 29 | 30 | export { calculateFee } from './calculator'; 31 | -------------------------------------------------------------------------------- /src/vms/pvm/utxoCalculationFns/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSpendableLockedUTXOs'; 2 | export * from './useUnlockedUTXOs'; 3 | export * from './useConsolidateOutputs'; 4 | -------------------------------------------------------------------------------- /src/vms/pvm/utxoCalculationFns/useConsolidateOutputs.ts: -------------------------------------------------------------------------------- 1 | import { consolidateOutputs } from '../../utils/consolidateOutputs'; 2 | import type { UTXOCalculationState } from '../../utils/calculateSpend'; 3 | 4 | export function useConsolidateOutputs({ 5 | changeOutputs, 6 | stakeOutputs, 7 | ...state 8 | }: UTXOCalculationState): UTXOCalculationState { 9 | const consolidatedChangeOutputs = consolidateOutputs(changeOutputs); 10 | const consolidatedStakeOutputs = consolidateOutputs(stakeOutputs); 11 | 12 | return { 13 | ...state, 14 | changeOutputs: consolidatedChangeOutputs, 15 | stakeOutputs: consolidatedStakeOutputs, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/vms/pvm/utxoCalculationFns/useUnlockedUTXOs.test.ts: -------------------------------------------------------------------------------- 1 | import { fromAddressBytes, getValidUtxo } from '../../../fixtures/transactions'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import { addressesFromBytes } from '../../../utils'; 5 | import { testContext } from '../../../fixtures/context'; 6 | import { useUnlockedUTXOs } from './useUnlockedUTXOs'; 7 | import { BigIntPr } from '../../../serializable/primitives'; 8 | import { defaultSpendResult } from '../../utils/calculateSpend'; 9 | 10 | describe('src/vms/pvm/spend/useUnlockedUTXOs.spec.ts', () => { 11 | it('Should pay gas and stake amount plus have some change', () => { 12 | const { changeOutputs, stakeOutputs, amountsToBurn } = useUnlockedUTXOs({ 13 | ...defaultSpendResult(), 14 | amountsToBurn: new Map([[testContext.avaxAssetID, 4900n]]), 15 | utxos: [getValidUtxo(new BigIntPr(10000n))], 16 | fromAddresses: addressesFromBytes(fromAddressBytes), 17 | amountsToStake: new Map([[testContext.avaxAssetID, 4900n]]), 18 | options: { changeAddresses: fromAddressBytes } as any, 19 | }); 20 | expect(changeOutputs.length).toEqual(1); 21 | expect(BigInt(changeOutputs[0].output.amount())).toEqual(BigInt(200n)); 22 | 23 | expect(stakeOutputs.length).toEqual(1); 24 | expect(BigInt(stakeOutputs[0].output.amount())).toEqual(BigInt(4900n)); 25 | 26 | expect(amountsToBurn.get(testContext.avaxAssetID)).toEqual(0n); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/vms/utils/calculateSpend/index.ts: -------------------------------------------------------------------------------- 1 | export * from './calculateSpend'; 2 | export * from './models'; 3 | -------------------------------------------------------------------------------- /src/vms/utils/calculateSpend/models.ts: -------------------------------------------------------------------------------- 1 | import type { Utxo } from '../../../serializable/avax/utxo'; 2 | import type { 3 | TransferableOutput, 4 | TransferableInput, 5 | } from '../../../serializable'; 6 | import type { Address } from '../../../serializable/fxs/common'; 7 | import type { SpendOptionsRequired } from '../../common'; 8 | import type { AddressMaps } from '../../../utils/addressMap'; 9 | 10 | export interface UTXOCalculationResult { 11 | inputs: TransferableInput[]; 12 | inputUTXOs: Utxo[]; 13 | stakeOutputs: TransferableOutput[]; 14 | changeOutputs: TransferableOutput[]; 15 | addressMaps: AddressMaps; 16 | } 17 | 18 | export interface UTXOCalculationState extends UTXOCalculationResult { 19 | amountsToBurn: Map; 20 | utxos: Utxo[]; 21 | fromAddresses: Address[]; 22 | amountsToStake: Map; 23 | options: SpendOptionsRequired; 24 | } 25 | 26 | export type UTXOCalculationFn = ( 27 | values: UTXOCalculationState, 28 | ) => UTXOCalculationState; 29 | -------------------------------------------------------------------------------- /src/vms/utils/calculateSpend/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './verifySignaturesMatch'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "moduleResolution": "node", 9 | "noEmit": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "module": "ESNext", 16 | "removeComments": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "target": "esnext", 20 | "outDir": "./dist", 21 | "baseUrl": "src", 22 | "rootDir": "src", 23 | "strictNullChecks": true, 24 | "noImplicitAny": false, 25 | "strictBindCallApply": false 26 | }, 27 | "include": ["src", "index.d.ts"], 28 | "ts-node": { 29 | "esm": true, 30 | "include": ["index.ts"], 31 | "experimentalSpecifierResolution": "node" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | provider: 'v8', 7 | }, 8 | clearMocks: true, 9 | root: 'src', 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------