├── .eslintrc.json ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package.json ├── prettierrc.json ├── src ├── config.ts ├── consts.ts ├── factory │ ├── factories.ts │ ├── index.ts │ └── utils.ts ├── helpers │ ├── algorand.ts │ ├── aptos │ │ ├── bridge_client.ts │ │ ├── bridge_client_abis.ts │ │ └── index.ts │ ├── bridge_pool.teal.ts │ ├── casper │ │ ├── casper.ts │ │ └── wait.ts │ ├── chain.ts │ ├── dfinity │ │ ├── dfinity.ts │ │ ├── idl.ts │ │ ├── ledger.did.ts │ │ ├── minter.did.d.ts │ │ └── xpnft.idl.ts │ ├── elrond │ │ ├── elrond-test.ts │ │ ├── elrond.ts │ │ └── v3Bridge_abi.json │ ├── evm │ │ ├── web3.ts │ │ ├── web3_erc20.ts │ │ └── web3_utils.ts │ ├── hedera │ │ ├── hedera_refactor.ts │ │ └── hts_abi.ts │ ├── near.ts │ ├── secret.ts │ ├── solana │ │ ├── idl.ts │ │ └── index.ts │ ├── tezos.ts │ ├── ton │ │ ├── nwl.ts │ │ ├── ton-bridge.ts │ │ ├── ton.ts │ │ └── v3types.ts │ └── tron.ts ├── index.ts ├── scripts │ └── deploy_tron.ts ├── services │ ├── emitter.ts │ ├── estimator │ │ └── index.ts │ ├── exchangeRate │ │ └── index.ts │ ├── heartbeat │ │ ├── index.ts │ │ └── resp.ts │ ├── hederaApi.ts │ ├── multiversex.ts │ ├── nftList.ts │ ├── notifier │ │ └── index.ts │ ├── scVerify.ts │ └── whitelisted.ts ├── socket.ts └── type-utils.ts ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "project": "./tsconfig.json" }, 5 | "env": { "es6": true }, 6 | "ignorePatterns": ["node_modules", "build", "coverage"], 7 | "plugins": ["import", "eslint-comments", "functional"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:eslint-comments/recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:import/typescript", 13 | "prettier", 14 | "prettier/@typescript-eslint", 15 | "plugin:functional/external-recommended", 16 | "plugin:functional/recommended", 17 | "plugin:functional/stylitic" 18 | ], 19 | "globals": { "BigInt": true, "console": true, "WebAssembly": true }, 20 | "rules": { 21 | "@typescript-eslint/explicit-module-boundary-types": "off", 22 | "eslint-comments/disable-enable-pair": [ 23 | "error", 24 | { "allowWholeFile": true } 25 | ], 26 | "eslint-comments/no-unused-disable": "error", 27 | "import/order": ["error", { "alphabetize": { "order": "asc" } }], 28 | "sort-imports": [ 29 | "error", 30 | { "ignoreDeclarationSort": true, "ignoreCase": true } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/publish.yml 2 | name: Generate a build and push to another branch 3 | 4 | on: 5 | push: 6 | branches: 7 | - "*" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | name: Build and Push 14 | steps: 15 | - name: git-checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: setup node.js 19 | uses: actions/setup-node@v2.4.0 20 | with: 21 | node-version: 16.x 22 | # cache: yarn 23 | # cache-dependency-path: yarn.lock 24 | 25 | - name: dep 26 | run: yarn 27 | 28 | - name: build 29 | run: yarn build 30 | 31 | - name: Generate Docs 32 | run: npx typedoc --out docs src/index.ts 33 | 34 | - name: prepare package 35 | run: mkdir out && mv package.json out && mv dist out && mv README.md out && mv LICENSE out && mv docs out 36 | 37 | - name: Prepare Production Release 38 | uses: s0/git-publish-subdir-action@develop 39 | if: ${{ github.ref == 'refs/heads/production' }} 40 | env: 41 | REPO: self 42 | BRANCH: bleeding-edge 43 | FOLDER: out 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | MESSAGE: "Build: ({sha}) {msg}" 46 | 47 | - name: Prepare Testnet Release 48 | uses: s0/git-publish-subdir-action@develop 49 | if: ${{ github.ref == 'refs/heads/testnet' }} 50 | env: 51 | REPO: self 52 | BRANCH: testnet-dist 53 | FOLDER: out 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | MESSAGE: "Build: ({sha}) {msg}" 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | build 3 | node_modules 4 | test 5 | dist 6 | src/**.js 7 | coverage 8 | *.log 9 | tsconfig.tsbuildinfo 10 | src/test.ts 11 | .env* 12 | test* 13 | *.DS_Store 14 | src/helpers/elrond/elrond-test.ts 15 | run1.ts -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 XP.network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xp.network", 3 | "version": "4.3.3", 4 | "author": "xp-network ", 5 | "description": "XP.Network Multi-Chain NFT Bridge JavaScript API", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "type": "commonjs", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/XP-NETWORK/xpjs" 13 | }, 14 | "scripts": { 15 | "build": "tsc -p tsconfig.json", 16 | "format": "prettier . --write", 17 | "prepare": "husky install" 18 | }, 19 | "devDependencies": { 20 | "@types/bn.js": "^5.1.0", 21 | "@types/node": "^18.7.13", 22 | "@typescript-eslint/eslint-plugin": "^4.31.0", 23 | "@typescript-eslint/parser": "^4.28.3", 24 | "dotenv": "^16.0.0", 25 | "eslint": "^7.30.0", 26 | "eslint-config-prettier": "^8.3.0", 27 | "eslint-plugin-eslint-comments": "^3.2.0", 28 | "eslint-plugin-functional": "^3.2.1", 29 | "husky": "^8.0.0", 30 | "prettier": "^2.3.2", 31 | "pretty-quick": "^3.1.3", 32 | "typedoc": "^0.22.6", 33 | "typescript": "^4.9.3", 34 | "yarn-audit-fix": "^9.3.7" 35 | }, 36 | "dependencies": { 37 | "@dfinity/agent": "^0.15.5", 38 | "@dfinity/candid": "^0.15.5", 39 | "@dfinity/identity-secp256k1": "^0.15.5", 40 | "@dfinity/nns": "^0.14.0", 41 | "@dfinity/principal": "^0.15.5", 42 | "@dfinity/utils": "^0.0.12", 43 | "@elrondnetwork/erdjs": "9.0.3", 44 | "@ethersproject/providers": "^5.7.0", 45 | "@hashgraph/hethers": "=1.1.2", 46 | "@json-rpc-tools/utils": "^1.7.6", 47 | "@make-software/ces-js-parser": "^1.3.1", 48 | "@multiversx/sdk-core": "^12.9.0", 49 | "@multiversx/sdk-extension-provider": "^3.0.0", 50 | "@multiversx/sdk-network-providers": "^2.2.0", 51 | "@multiversx/sdk-wallet": "^4.2.0", 52 | "@project-serum/anchor": "^0.25.0-beta.1", 53 | "@randlabs/myalgo-connect": "^1.1.1", 54 | "@solana/spl-token": "^0.2.0", 55 | "@solana/web3.js": "^1.44.2", 56 | "@taquito/signer": "^16.1.1", 57 | "@taquito/taquito": "^13.0.0", 58 | "@taquito/utils": "^13.0.1", 59 | "@vechain/connex-driver": "^2.0.8", 60 | "@vechain/connex-framework": "^2.0.8", 61 | "@walletconnect/client": "^1.7.0", 62 | "algosdk": "=2.2.0", 63 | "aptos": "^1.6.0", 64 | "axios": "^1.6.2", 65 | "base64url": "^3.0.1", 66 | "bignumber.js": "=9.0.1", 67 | "bn.js": "^5.2.1", 68 | "buffer": "^6.0.3", 69 | "casper-cep78-js-client": "^1.4.0", 70 | "casper-js-sdk": "^2.13.3", 71 | "crypto-exchange-rate": "git+https://github.com/xp-network/exchange-rate#master-dist", 72 | "ethers": "^5.5.4", 73 | "js-base64": "^3.6.1", 74 | "near-api-js": "1.0.0", 75 | "newton": "npm:ton@latest", 76 | "secretjs": "=1.4.7", 77 | "socket.io-client": "^4.1.3", 78 | "ton-core": "^0.53.0", 79 | "ton-x": "^2.0.0", 80 | "tonweb": "^0.0.57", 81 | "tonweb-mnemonic": "^1.0.1", 82 | "tronstation": "^1.0.1", 83 | "tronweb": "^4.1.0", 84 | "web3-providers-connex": "^0.3.1", 85 | "xpbridge-client": "git+https://github.com/XP-NETWORK/xp-casper-bridge-client#73fd2535e9f2fe0a80c44edc9fccd5c33dc1ec6f", 86 | "xpnet-web3-contracts": "git+https://github.com/xp-network/XP.network-HECO-Migration#dist-erc1155" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "insertPragma": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 80, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": true, 13 | "singleAttributePerLine": false, 14 | "singleQuote": true, 15 | "tabWidth": 4, 16 | "trailingComma": "es5", 17 | "useTabs": false 18 | } 19 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { AppConfig, ChainFactory, ChainFactoryConfigs } from "."; 2 | 3 | export namespace AppConfigs { 4 | export const MainNet: () => AppConfig = () => { 5 | return { 6 | exchangeRateUri: "https://tools.xp.network/exchange-rate/exchange/", 7 | nftListUri: "https://nft-index.xp.network/index/", 8 | whitelistedUri: "https://nft-index.xp.network/", 9 | nftListAuthToken: 10 | "eyJhbGciOiJFUzI1NiJ9.eyJhdXRob3JpdHkiOjEsImlhdCI6MTY1Mjc5MTU1NiwiZXhwIjoxNjY4MzQzNTU2fQ.gOzLCBPNGFfjqLzSZsMes0yplAhsRiQYzidVfE-IYtQ-aVqQU6LhzKevLxYLudnm28F5_7CzTKsiuUginuLTtQ", 11 | txSocketUri: "https://transaction-socket.xp.network", 12 | tronScanUri: "https://apilist.tronscan.org/api/", 13 | heartbeatUri: "https://xpheartbeat.herokuapp.com", 14 | wrappedNftPrefix: "https://nft.xp.network/w/", 15 | scVerifyUri: "https://sc-verify.xp.network", 16 | storageContract: "", 17 | storegeNetwork: "", 18 | network: "mainnet", 19 | }; 20 | }; 21 | export const TestNet: () => AppConfig = () => { 22 | return { 23 | exchangeRateUri: "https://tools.xp.network/exchange-rate/exchange/", 24 | nftListUri: "https://tools.xp.network/testnet-indexer/", 25 | whitelistedUri: "https://tools.xp.network/testnet-notifier/", 26 | nftListAuthToken: 27 | "eyJhbGciOiJFUzI1NiJ9.eyJhdXRob3JpdHkiOjEsImlhdCI6MTY1MjA4NzAwNiwiZXhwIjoxNjU5ODYzMDA2fQ.ERjXpljkyuklPTJCSXQXZ-Wh09oxQwA4u8HKIbIwO1TSajvLIlNgxseqBVEd5D4lkqXYGwcezkuezuRc3kKkKg", 28 | txSocketUri: "https://tools.xp.network/testnet-tx-socket", //"https://testnet-tx-socket.herokuapp.com", 29 | tronScanUri: "https://apilist.tronscan.org/api/", 30 | heartbeatUri: "https://tools.xp.network/testnet-pinger/", 31 | wrappedNftPrefix: "https://tools.xp.network/testnet-wnft/", //"https://bridge-wnftapi.herokuapp.com/", 32 | scVerifyUri: "https://tools.xp.network/testnet-sc-verify/", 33 | storageContract: "0x0263A038014505881E33d4201950fa11c29793F3", //"0x8F1fd3a5DbBd5659579aE7d9b258CC6CbcB3e53d", //"0xDb2a42f40158B1Cb29703e2a95a6fa3094294f05", 34 | storegeNetwork: "https://optimism-goerli.publicnode.com", 35 | network: "testnet", 36 | }; 37 | }; 38 | 39 | export const Staging: () => AppConfig = () => { 40 | return { 41 | exchangeRateUri: "https://tools.xp.network/exchange-rate/exchange/", 42 | nftListUri: "https://tools.xp.network/index/", 43 | whitelistedUri: "https://tools.xp.network/notifier/", 44 | nftListAuthToken: 45 | "eyJhbGciOiJFUzI1NiJ9.eyJhdXRob3JpdHkiOjEsImlhdCI6MTY1Mjc5MTU1NiwiZXhwIjoxNjY4MzQzNTU2fQ.gOzLCBPNGFfjqLzSZsMes0yplAhsRiQYzidVfE-IYtQ-aVqQU6LhzKevLxYLudnm28F5_7CzTKsiuUginuLTtQ", 46 | txSocketUri: "https://tools.xp.network/tx-socket/", //"https://staging-tx-socket-925db65784a7.herokuapp.com/", 47 | tronScanUri: "https://apilist.tronscan.org/api/", 48 | heartbeatUri: "https://xpheartbeat.herokuapp.com", 49 | wrappedNftPrefix: "https://tools.xp.network/wnft/", //"https://staging-nft.xp.network/w/", 50 | scVerifyUri: "https://tools.xp.network/sc-verify", 51 | storageContract: "", 52 | storegeNetwork: "", 53 | network: "staging", 54 | }; 55 | }; 56 | } 57 | 58 | export namespace ChainFactories { 59 | export const MainNet = async () => { 60 | return ChainFactory( 61 | AppConfigs.MainNet(), 62 | await ChainFactoryConfigs.MainNet() 63 | ); 64 | }; 65 | 66 | export const TestNet = async () => { 67 | return ChainFactory( 68 | AppConfigs.TestNet(), 69 | await ChainFactoryConfigs.TestNet() 70 | ); 71 | }; 72 | export const Staging = async () => { 73 | return ChainFactory( 74 | AppConfigs.Staging(), 75 | await ChainFactoryConfigs.Staging() 76 | ); 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /src/factory/utils.ts: -------------------------------------------------------------------------------- 1 | import { NftInfo, FullChain } from ".."; 2 | import { CHAIN_INFO, ChainType, Chain } from "../consts"; 3 | import axios from "axios"; 4 | import BigNumber from "bignumber.js"; 5 | import { V3_ChainId } from "../type-utils"; 6 | 7 | export const _headers = { 8 | "Content-Type": "application/json", 9 | Accept: "*/*", 10 | }; 11 | 12 | export const oldXpWraps = new Set([ 13 | "0xe12B16FFBf7D79eb72016102F3e3Ae6fe03fCA56", 14 | "0xc69ECD37122A9b5FD7e62bC229d478BB83063C9d", 15 | "0xe12B16FFBf7D79eb72016102F3e3Ae6fe03fCA56", 16 | "0xa1B8947Ff4C1fD992561F629cfE67aEb90DfcBd5", 17 | "0x09F4e56187541f2bC660B0810cA509D2f8c65c96", 18 | "0x8B2957DbDC69E158aFceB9822A2ff9F2dd5BcD65", 19 | "0xE773Be36b35e7B58a9b23007057b5e2D4f6686a1", 20 | "0xFC2b3dB912fcD8891483eD79BA31b8E5707676C9", 21 | "0xb4A252B3b24AF2cA83fcfdd6c7Fac04Ff9d45A7D", 22 | ]); 23 | 24 | export function checkBlockedContracts(to: any, contract: string) { 25 | const chain = CHAIN_INFO.get(to); 26 | if (chain?.rejectUnfreeze && chain?.rejectUnfreeze.includes(contract)) { 27 | throw new Error( 28 | `Transfering to ${chain.name} is prohibited by the NFT project team` 29 | ); 30 | } 31 | } 32 | 33 | export function getDefaultContract( 34 | nft: NftInfo, 35 | fromChain: FullChain, 36 | toChain: FullChain 37 | ): string | undefined { 38 | const defaultMintError = new Error( 39 | `Transfer has been canceled. The NFT you are trying to send will be minted with a default NFT collection` 40 | ); 41 | 42 | const from = fromChain.getNonce(); 43 | const to = toChain.getNonce(); 44 | 45 | const fromType = CHAIN_INFO.get(from)?.type; 46 | const toType = CHAIN_INFO.get(to)?.type; 47 | 48 | const contract = 49 | //@ts-ignore contractType is checked 50 | "contractType" in nft.native && 51 | //@ts-ignore contractType is checked 52 | nft.native.contractType === "ERC1155" && 53 | toChain.XpNft1155 54 | ? toChain.XpNft1155 55 | : toChain.XpNft; 56 | 57 | if ( 58 | typeof window !== "undefined" && 59 | (/(allowDefaultMint=true)/.test(window.location.search) || 60 | /testnet/.test(window.location.pathname)) 61 | ) { 62 | return contract; 63 | } 64 | 65 | if ( 66 | (from === Chain.VECHAIN && toType === ChainType.EVM) || 67 | (to === Chain.VECHAIN && fromType === ChainType.EVM) 68 | ) { 69 | throw defaultMintError; 70 | } 71 | 72 | if ( 73 | (fromType === ChainType.EVM && toType === ChainType.ELROND) || 74 | (fromType === ChainType.ELROND && toType === ChainType.EVM) 75 | ) { 76 | throw defaultMintError; 77 | } 78 | 79 | // if ( 80 | // (fromType === ChainType.EVM && toType === ChainType.TEZOS) || 81 | // (fromType === ChainType.TEZOS && toType === ChainType.EVM) 82 | // ) { 83 | // throw defaultMintError; 84 | // } 85 | 86 | if (from === Chain.SECRET) { 87 | throw defaultMintError; 88 | } 89 | 90 | if (fromType === ChainType.TRON) { 91 | throw defaultMintError; 92 | } 93 | 94 | return contract; 95 | } 96 | 97 | export function prepareTokenId(nft: NftInfo, from: number) { 98 | const tokenId = 99 | //@ts-ignore 100 | nft.native && "tokenId" in nft.native && nft.native.tokenId.toString(); 101 | 102 | if (tokenId) { 103 | const notNumber = isNaN(Number(tokenId)); 104 | 105 | if (notNumber) { 106 | if (from === Chain.ELROND) { 107 | if (nft.native.nonce) return String(nft.native.nonce); 108 | const hex = tokenId.split("-")?.at(2); 109 | return String(hex ? parseInt(hex, 16) : ""); 110 | } 111 | 112 | if (from === Chain.TON || from === Chain.SECRET) { 113 | return "1"; 114 | } 115 | } else { 116 | return tokenId; 117 | } 118 | } 119 | return undefined; 120 | } 121 | 122 | export function checkNotOldWrappedNft(contract: string) { 123 | if (oldXpWraps.has(contract)) { 124 | throw new Error(`${contract} is an old wrapped NFT`); 125 | } 126 | } 127 | 128 | export async function isWrappedNft(nft: NftInfo, fc: number, tc?: number) { 129 | if (fc === Chain.TEZOS) { 130 | return { 131 | bool: 132 | typeof (nft.native as any).meta?.token?.metadata?.wrapped !== 133 | "undefined", 134 | wrapped: undefined, 135 | }; 136 | } 137 | 138 | if (nft.native.metadata?.wrapped) { 139 | return { bool: true, wrapped: nft.native.metadata.wrapped }; 140 | } 141 | 142 | try { 143 | checkNotOldWrappedNft(nft.collectionIdent); 144 | } catch (_) { 145 | return { bool: false, wrapped: undefined }; 146 | } 147 | 148 | if (/w\/$/.test(nft.uri)) { 149 | nft = { 150 | ...nft, 151 | uri: nft.uri + nft.native.tokenId, 152 | }; 153 | } 154 | 155 | const wrapped = (await axios.get(nft.uri).catch(() => undefined))?.data 156 | .wrapped; 157 | const contract = wrapped?.contract || wrapped?.source_mint_ident; 158 | tc && contract && checkBlockedContracts(tc, contract); 159 | 160 | return { bool: typeof wrapped !== "undefined", wrapped }; 161 | } 162 | 163 | export const randomBigInt = () => 164 | BigInt(new BigNumber(Math.random() * 150_000).integerValue().toString()); 165 | 166 | export const decodeBase64Array = (encodedArray: string[]): string[] | null => { 167 | return encodedArray.map((encodedString) => { 168 | return Buffer.from(encodedString, "base64").toString("utf-8"); 169 | }); 170 | }; 171 | 172 | export const v3BridgeIdToNonce = (id: V3_ChainId) => 173 | Array.from(CHAIN_INFO.values()) 174 | .find((c) => c.v3_chainId === id)! 175 | .nonce.toString(); 176 | -------------------------------------------------------------------------------- /src/helpers/aptos/bridge_client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AptosAccount, 3 | AptosClient, 4 | HexString, 5 | MaybeHexString, 6 | TransactionBuilderABI, 7 | } from "aptos"; 8 | import { 9 | MAINNET_BRIDGE_ABIS, 10 | STAGING_BRIDGE_ABIS, 11 | TESTNET_BRIDGE_ABIS, 12 | } from "./bridge_client_abis"; 13 | 14 | interface BridgeData { 15 | action_cnt: string; 16 | burning_nfts: { 17 | handle: string; 18 | }; 19 | consumed_actions: { 20 | handle: string; 21 | }; 22 | group_key: string; 23 | paused: boolean; 24 | frozen_nfts: { 25 | handle: string; 26 | }; 27 | whitelist: { 28 | handle: string; 29 | }; 30 | } 31 | 32 | export class BridgeClient { 33 | private aptosClient: AptosClient; 34 | private transactionBuilder: TransactionBuilderABI; 35 | private address: string; 36 | 37 | constructor( 38 | aptosClient: AptosClient, 39 | address: string, 40 | network: "mainnet" | "staging" | "testnet" 41 | ) { 42 | this.aptosClient = aptosClient; 43 | let abi; 44 | switch (network) { 45 | case "mainnet": 46 | abi = MAINNET_BRIDGE_ABIS; 47 | break; 48 | case "staging": 49 | abi = STAGING_BRIDGE_ABIS; 50 | break; 51 | case "testnet": 52 | abi = TESTNET_BRIDGE_ABIS; 53 | break; 54 | default: 55 | throw new Error("Invalid network"); 56 | } 57 | this.transactionBuilder = new TransactionBuilderABI( 58 | abi.map((abi) => new HexString(abi).toUint8Array()) 59 | ); 60 | this.address = address; 61 | } 62 | 63 | async initialize( 64 | account: AptosAccount, 65 | groupKey: Uint8Array 66 | ): Promise { 67 | const payload = this.transactionBuilder.buildTransactionPayload( 68 | `${this.getAddress()}::bridge::initialize`, 69 | [], 70 | [groupKey] 71 | ); 72 | 73 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 74 | } 75 | 76 | async pause( 77 | account: AptosAccount, 78 | actionId: number | bigint, 79 | signature: Uint8Array 80 | ): Promise { 81 | const payload = this.transactionBuilder.buildTransactionPayload( 82 | `${this.getAddress()}::bridge::pause`, 83 | [], 84 | [actionId, signature] 85 | ); 86 | 87 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 88 | } 89 | 90 | async unpause( 91 | account: AptosAccount, 92 | actionId: number | bigint, 93 | signature: Uint8Array 94 | ): Promise { 95 | const payload = this.transactionBuilder.buildTransactionPayload( 96 | `${this.getAddress()}::bridge::unpause`, 97 | [], 98 | [actionId, signature] 99 | ); 100 | 101 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 102 | } 103 | 104 | async validateWhitelist( 105 | account: AptosAccount, 106 | collectionCreator: HexString, 107 | collectionName: string, 108 | actionId: number | bigint, 109 | signature: Uint8Array 110 | ): Promise { 111 | const payload = this.transactionBuilder.buildTransactionPayload( 112 | `${this.getAddress()}::bridge::validate_whitelist`, 113 | [], 114 | [collectionCreator.toString(), collectionName, actionId, signature] 115 | ); 116 | 117 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 118 | } 119 | 120 | async validateBlacklist( 121 | account: AptosAccount, 122 | collectionCreator: HexString, 123 | collectionName: string, 124 | actionId: number | bigint, 125 | signature: Uint8Array 126 | ): Promise { 127 | const payload = this.transactionBuilder.buildTransactionPayload( 128 | `${this.getAddress()}::bridge::validate_blacklist`, 129 | [], 130 | [collectionCreator.toString(), collectionName, actionId, signature] 131 | ); 132 | 133 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 134 | } 135 | 136 | async validateWithdrawFees( 137 | account: AptosAccount, 138 | to: HexString, 139 | actionId: number | bigint, 140 | signature: Uint8Array 141 | ): Promise { 142 | const payload = this.transactionBuilder.buildTransactionPayload( 143 | `${this.getAddress()}::bridge::validate_withdraw_fees`, 144 | [], 145 | [to.toString(), actionId, signature] 146 | ); 147 | 148 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 149 | } 150 | 151 | async validateTransferNft( 152 | account: AptosAccount, 153 | collection: string, 154 | name: string, 155 | description: string, 156 | maximum: number | bigint, 157 | uri: string, 158 | royaltyPayeeAddress: HexString, 159 | royaltyPointsDenominator: number | bigint, 160 | royaltyPointsNumerator: number | bigint, 161 | mutateSetting: boolean[], 162 | to: HexString, 163 | actionId: number | bigint, 164 | signature: Uint8Array 165 | ): Promise { 166 | const payload = this.transactionBuilder.buildTransactionPayload( 167 | `${this.getAddress()}::bridge::validate_transfer_nft`, 168 | [], 169 | [ 170 | collection, 171 | name, 172 | description, 173 | maximum, 174 | uri, 175 | royaltyPayeeAddress.toString(), 176 | royaltyPointsDenominator.toString(), 177 | royaltyPointsNumerator.toString(), 178 | mutateSetting, 179 | to.toString(), 180 | actionId, 181 | signature, 182 | ] 183 | ); 184 | 185 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 186 | } 187 | 188 | async withdrawNft( 189 | account: AptosAccount, 190 | _bridgeAdmin: HexString, 191 | collectionCreator: HexString, 192 | collectionName: string, 193 | tokenName: string, 194 | propertyVersion: string, 195 | price: number | bigint, 196 | chainNonce: number | bigint, 197 | to: string, 198 | mintWith: string 199 | ): Promise { 200 | const payload = this.transactionBuilder.buildTransactionPayload( 201 | `${this.getAddress()}::bridge::withdraw_nft`, 202 | [], 203 | [ 204 | collectionCreator.toString(), 205 | collectionName, 206 | tokenName, 207 | propertyVersion, 208 | price, 209 | chainNonce, 210 | to, 211 | mintWith, 212 | ] 213 | ); 214 | 215 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 216 | } 217 | 218 | async validateBurnNft( 219 | account: AptosAccount, 220 | collectionCreator: HexString, 221 | collectionName: string, 222 | tokenName: string, 223 | propertyVersion: string, 224 | actionId: number | bigint, 225 | signature: Uint8Array 226 | ): Promise { 227 | const payload = this.transactionBuilder.buildTransactionPayload( 228 | `${this.getAddress()}::bridge::validate_burn_nft`, 229 | [], 230 | [ 231 | collectionCreator.toString(), 232 | collectionName, 233 | tokenName, 234 | propertyVersion, 235 | actionId, 236 | signature, 237 | ] 238 | ); 239 | 240 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 241 | } 242 | 243 | async freezeNft( 244 | account: AptosAccount, 245 | collectionCreator: HexString, 246 | collectionName: string, 247 | tokenName: string, 248 | propertyVersion: number | bigint, 249 | price: number | bigint, 250 | chainNonce: number | bigint, 251 | to: string, 252 | mintWith: string 253 | ): Promise { 254 | const payload = this.transactionBuilder.buildTransactionPayload( 255 | `${this.getAddress()}::bridge::freeze_nft`, 256 | [], 257 | [ 258 | collectionCreator.toString(), 259 | collectionName, 260 | tokenName, 261 | propertyVersion, 262 | price, 263 | chainNonce, 264 | to, 265 | mintWith, 266 | ] 267 | ); 268 | 269 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 270 | } 271 | 272 | async validateUnfreezeNft( 273 | account: AptosAccount, 274 | collectionCreator: HexString, 275 | collectionName: string, 276 | tokenName: string, 277 | propertyVersion: string, 278 | to: HexString, 279 | actionId: number | bigint, 280 | signature: Uint8Array 281 | ): Promise { 282 | const payload = this.transactionBuilder.buildTransactionPayload( 283 | `${this.getAddress()}::bridge::validate_unfreeze_nft`, 284 | [], 285 | [ 286 | collectionCreator.toString(), 287 | collectionName, 288 | tokenName, 289 | propertyVersion, 290 | to.toString(), 291 | actionId, 292 | signature, 293 | ] 294 | ); 295 | 296 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 297 | } 298 | 299 | async updateGroupKey( 300 | account: AptosAccount, 301 | groupKey: Uint8Array, 302 | actionId: number | bigint, 303 | signature: Uint8Array 304 | ): Promise { 305 | const payload = this.transactionBuilder.buildTransactionPayload( 306 | `${this.getAddress()}::bridge::update_group_key`, 307 | [], 308 | [groupKey, actionId, signature] 309 | ); 310 | 311 | return this.aptosClient.generateSignSubmitTransaction(account, payload); 312 | } 313 | 314 | async getBridgeData() { 315 | const resources = await this.aptosClient.getAccountResources( 316 | this.getAddress() 317 | ); 318 | const accountResource = resources.find( 319 | (r) => r.type == `${this.getAddress()}::bridge::Bridge` 320 | ); 321 | return accountResource?.data as BridgeData; 322 | } 323 | 324 | getAddress() { 325 | return this.address; 326 | } 327 | 328 | async isWhitelist(collectionCreator: MaybeHexString, collectionName: string) { 329 | const data = await this.getBridgeData(); 330 | const { handle } = data.whitelist; 331 | try { 332 | const res = await this.aptosClient.getTableItem(handle, { 333 | key_type: `${this.getAddress()}::bridge::CollectionId`, 334 | value_type: "bool", 335 | key: { 336 | creator: collectionCreator.toString(), 337 | name: collectionName, 338 | }, 339 | }); 340 | return res; 341 | } catch (e: any) { 342 | return false; 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/helpers/aptos/bridge_client_abis.ts: -------------------------------------------------------------------------------- 1 | export const MAINNET_BRIDGE_ABIS = [ 2 | // ../build/aptos-bridge/abis/bridge/initialize.abi 3 | "010a696e697469616c697a65f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d066272696467650000010967726f75705f6b65790601", 4 | // ../build/aptos-bridge/abis/bridge/pause.abi 5 | "01057061757365f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000209616374696f6e5f696403097369676e61747572650601", 6 | // ../build/aptos-bridge/abis/bridge/unpause.abi 7 | "0107756e7061757365f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000209616374696f6e5f696403097369676e61747572650601", 8 | // ../build/aptos-bridge/abis/bridge/update_group_key.abi 9 | "01107570646174655f67726f75705f6b6579f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d066272696467650000030d6e65775f67726f75705f6b6579060109616374696f6e5f696403097369676e61747572650601", 10 | // ../build/aptos-bridge/abis/bridge/validate_whitelist.abi 11 | "011276616c69646174655f77686974656c697374f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 12 | // ../build/aptos-bridge/abis/bridge/validate_blacklist.abi 13 | "011276616c69646174655f626c61636b6c697374f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 14 | // ../build/aptos-bridge/abis/bridge/validate_withdraw_fees.abi 15 | "011676616c69646174655f77697468647261775f66656573f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000302746f0409616374696f6e5f696403097369676e61747572650601", 16 | // ../build/aptos-bridge/abis/bridge/validate_tranfer_nft.abi 17 | "011576616c69646174655f7472616e736665725f6e6674f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000f0a636f6c6c656374696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700046e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000b6465736372697074696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700076d6178696d756d020375726907000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670015726f79616c74795f70617965655f61646472657373041a726f79616c74795f706f696e74735f64656e6f6d696e61746f720218726f79616c74795f706f696e74735f6e756d657261746f72020e6d75746174655f73657474696e6706000d70726f70657274795f6b6579730607000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000f70726f70657274795f76616c7565730606010e70726f70657274795f74797065730607000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670002746f0409616374696f6e5f696403097369676e61747572650601", 18 | // ../build/aptos-bridge/abis/bridge/withdraw_nft.abi 19 | "010c77697468647261775f6e6674f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 20 | // ../build/aptos-bridge/abis/bridge/validate_burn_nft.abi 21 | "011176616c69646174655f6275726e5f6e6674f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000612636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e0209616374696f6e5f696403097369676e61747572650601", 22 | // ../build/aptos-bridge/abis/bridge/freeze_nft.abi 23 | "010a667265657a655f6e6674f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 24 | // ../build/aptos-bridge/abis/bridge/validate_unfreeze_nft.abi 25 | "011576616c69646174655f756e667265657a655f6e6674f1528ec9c413e37f0ce63eb699fa6c7521b925ff4857b2c95e857e43078c916d0662726964676500000712636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e0202746f0409616374696f6e5f696403097369676e61747572650601", 26 | ]; 27 | 28 | export const TESTNET_BRIDGE_ABIS = [ 29 | // ../build/aptos-bridge/abis/bridge/initialize.abi 30 | "010a696e697469616c697a65bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd07066272696467650000010967726f75705f6b65790601", 31 | // ../build/aptos-bridge/abis/bridge/pause.abi 32 | "01057061757365bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000209616374696f6e5f696403097369676e61747572650601", 33 | // ../build/aptos-bridge/abis/bridge/unpause.abi 34 | "0107756e7061757365bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000209616374696f6e5f696403097369676e61747572650601", 35 | // ../build/aptos-bridge/abis/bridge/update_group_key.abi 36 | "01107570646174655f67726f75705f6b6579bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd07066272696467650000030d6e65775f67726f75705f6b6579060109616374696f6e5f696403097369676e61747572650601", 37 | // ../build/aptos-bridge/abis/bridge/validate_whitelist.abi 38 | "011276616c69646174655f77686974656c697374bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 39 | // ../build/aptos-bridge/abis/bridge/validate_blacklist.abi 40 | "011276616c69646174655f626c61636b6c697374bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 41 | // ../build/aptos-bridge/abis/bridge/validate_withdraw_fees.abi 42 | "011676616c69646174655f77697468647261775f66656573bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000302746f0409616374696f6e5f696403097369676e61747572650601", 43 | // ../build/aptos-bridge/abis/bridge/validate_tranfer_nft.abi 44 | "011576616c69646174655f7472616e736665725f6e6674bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000c0a636f6c6c656374696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700046e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000b6465736372697074696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700076d6178696d756d020375726907000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670015726f79616c74795f70617965655f61646472657373041a726f79616c74795f706f696e74735f64656e6f6d696e61746f720218726f79616c74795f706f696e74735f6e756d657261746f72020e6d75746174655f73657474696e67060002746f0409616374696f6e5f696403097369676e61747572650601", 45 | // ../build/aptos-bridge/abis/bridge/withdraw_nft.abi 46 | "010c77697468647261775f6e6674bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 47 | // ../build/aptos-bridge/abis/bridge/freeze_nft.abi 48 | "010a667265657a655f6e6674bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 49 | // ../build/aptos-bridge/abis/bridge/validate_unfreeze_nft.abi 50 | "011576616c69646174655f756e667265657a655f6e6674bfe94f90e13feb17c09c638dae30830b2f2d98f188071538bdaa4896377ecd070662726964676500000712636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e0202746f0409616374696f6e5f696403097369676e61747572650601", 51 | ]; 52 | export const STAGING_BRIDGE_ABIS = [ 53 | // ../build/aptos-bridge/abis/bridge/initialize.abi 54 | "010a696e697469616c697a6542ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e066272696467650000010967726f75705f6b65790601", 55 | // ../build/aptos-bridge/abis/bridge/pause.abi 56 | "0105706175736542ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000209616374696f6e5f696403097369676e61747572650601", 57 | // ../build/aptos-bridge/abis/bridge/unpause.abi 58 | "0107756e706175736542ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000209616374696f6e5f696403097369676e61747572650601", 59 | // ../build/aptos-bridge/abis/bridge/update_group_key.abi 60 | "01107570646174655f67726f75705f6b657942ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e066272696467650000030d6e65775f67726f75705f6b6579060109616374696f6e5f696403097369676e61747572650601", 61 | // ../build/aptos-bridge/abis/bridge/validate_whitelist.abi 62 | "011276616c69646174655f77686974656c69737442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 63 | // ../build/aptos-bridge/abis/bridge/validate_blacklist.abi 64 | "011276616c69646174655f626c61636b6c69737442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000412636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670009616374696f6e5f696403097369676e61747572650601", 65 | // ../build/aptos-bridge/abis/bridge/validate_withdraw_fees.abi 66 | "011676616c69646174655f77697468647261775f6665657342ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000302746f0409616374696f6e5f696403097369676e61747572650601", 67 | // ../build/aptos-bridge/abis/bridge/validate_tranfer_nft.abi 68 | "011576616c69646174655f7472616e736665725f6e667442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000c0a636f6c6c656374696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700046e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000b6465736372697074696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700076d6178696d756d020375726907000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670015726f79616c74795f70617965655f61646472657373041a726f79616c74795f706f696e74735f64656e6f6d696e61746f720218726f79616c74795f706f696e74735f6e756d657261746f72020e6d75746174655f73657474696e67060002746f0409616374696f6e5f696403097369676e61747572650601", 69 | // ../build/aptos-bridge/abis/bridge/withdraw_nft.abi 70 | "010c77697468647261775f6e667442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 71 | // ../build/aptos-bridge/abis/bridge/freeze_nft.abi 72 | "010a667265657a655f6e667442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000812636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e02057072696365020b636861696e5f6e6f6e63650202746f07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700096d696e745f7769746807000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700", 73 | // ../build/aptos-bridge/abis/bridge/validate_unfreeze_nft.abi 74 | "011576616c69646174655f756e667265657a655f6e667442ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e0662726964676500000712636f6c6c656374696f6e5f63726561746f72040f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000a746f6b656e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67001070726f70657274795f76657273696f6e0202746f0409616374696f6e5f696403097369676e61747572650601", 75 | // ../build/aptos-bridge/abis/bridge/create_collection.abi 76 | "01116372656174655f636f6c6c656374696f6e42ef1f5fcf8398a07c57d8320f510e82588bac408d820679918c0f87270e932e066272696467650000050f636f6c6c656374696f6e5f6e616d6507000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e670016636f6c6c656374696f6e5f6465736372697074696f6e07000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e67000e636f6c6c656374696f6e5f75726907000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700076d6178696d756d020e6d75746174655f73657474696e670600", 77 | ]; 78 | -------------------------------------------------------------------------------- /src/helpers/aptos/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChainNonceGet, 3 | EstimateTxFees, 4 | FeeMargins, 5 | GetFeeMargins, 6 | GetProvider, 7 | MintNft, 8 | TransferNftForeign, 9 | UnfreezeForeignNft, 10 | ValidateAddress, 11 | BalanceCheck, 12 | GetExtraFees, 13 | } from "../chain"; 14 | 15 | import { 16 | AptosAccount, 17 | AptosClient, 18 | HexString, 19 | CoinClient, 20 | TokenClient, 21 | } from "aptos"; 22 | 23 | import { Chain } from "../../consts"; 24 | import BigNumber from "bignumber.js"; 25 | import { BridgeClient } from "./bridge_client"; 26 | import { EvNotifier } from "../../services/notifier"; 27 | 28 | export type AptosNFT = { 29 | collection_creator: string; 30 | collection_name: string; 31 | token_name: string; 32 | property_version: number; 33 | }; 34 | 35 | /** 36 | * @param collection name of the collection u already own. if u dont own any token, then set this as undefined 37 | * @param name name of the NFT 38 | * @param description description of the NFT 39 | * @param uri The URI which the NFT points to 40 | * @param createCollection set this as true if u set collection as undefined. it will create a new collection. 41 | */ 42 | export type AptosMintArgs = { 43 | collection: string | undefined; 44 | name: string; 45 | description: string; 46 | uri: string; 47 | createCollection: boolean; 48 | }; 49 | 50 | export type AptosClaimArgs = { 51 | sender: HexString; 52 | propertyVersion: number; 53 | collectionName: string; 54 | creator: string; 55 | name: string; 56 | }; 57 | 58 | interface ClaimNFT { 59 | claimNFT(signer: Signer, args: ClaimArgs): Promise; 60 | } 61 | 62 | export type AptosHelper = ChainNonceGet & 63 | TransferNftForeign & 64 | UnfreezeForeignNft & 65 | EstimateTxFees & 66 | ValidateAddress & { 67 | XpNft: string; 68 | } & GetFeeMargins & 69 | MintNft & 70 | GetProvider & 71 | ClaimNFT & 72 | BalanceCheck & { 73 | setPetraSigner(signer: any): void; 74 | } & GetExtraFees; 75 | 76 | export type AptosParams = { 77 | feeMargin: FeeMargins; 78 | rpcUrl: string; 79 | xpnft: string; 80 | bridge: string; 81 | notifier: EvNotifier; 82 | network: "mainnet" | "staging" | "testnet"; 83 | }; 84 | 85 | export async function aptosHelper({ 86 | feeMargin, 87 | rpcUrl, 88 | xpnft, 89 | bridge, 90 | notifier, 91 | network, 92 | }: AptosParams): Promise { 93 | const client = new AptosClient(rpcUrl); 94 | 95 | const bridgeClient = new BridgeClient(client, bridge, network); 96 | const coinClient = new CoinClient(client); 97 | 98 | return { 99 | getNonce() { 100 | return Chain.APTOS; 101 | }, 102 | getFeeMargin() { 103 | return feeMargin; 104 | }, 105 | setPetraSigner(signer: any) { 106 | //imposter 107 | client.generateSignSubmitTransaction = async function ( 108 | _: AptosAccount, 109 | payload: any 110 | ) { 111 | const trx = await signer.signAndSubmitTransaction(payload); 112 | return trx.hash; 113 | }; 114 | }, 115 | balance: async (address) => { 116 | return new BigNumber((await coinClient.checkBalance(address)).toString()); 117 | }, 118 | async validateAddress(adr) { 119 | try { 120 | await client.getAccount(adr); 121 | return true; 122 | } catch (e) { 123 | return false; 124 | } 125 | }, 126 | XpNft: xpnft, 127 | 128 | async estimateValidateTransferNft(_to, _metadata, _mintWith) { 129 | return new BigNumber(0); 130 | }, 131 | async estimateValidateUnfreezeNft(_to, _metadata, _mintWith) { 132 | return new BigNumber(0); 133 | }, 134 | getExtraFees: () => { 135 | return new BigNumber(0); 136 | }, 137 | async transferNftToForeign( 138 | sender, 139 | chain_nonce, 140 | to, 141 | id, 142 | txFees, 143 | mintWith, 144 | _gasLimit? 145 | ) { 146 | const receipt = await bridgeClient.freezeNft( 147 | sender, 148 | HexString.ensure(id.native.collection_creator), 149 | id.native.collection_name, 150 | id.native.token_name, 151 | id.native.property_version, 152 | BigInt(txFees.toString()), 153 | chain_nonce, 154 | to, 155 | mintWith 156 | ); 157 | await new Promise((r) => setTimeout(r, 10000)); 158 | await notifier.notifyAptos(receipt); 159 | return receipt; 160 | }, 161 | getProvider() { 162 | return client; 163 | }, 164 | async mintNft(owner, options) { 165 | //AptosAccount.fromAptosAccountObject({""}) 166 | const tc = new TokenClient(client); 167 | if (options.createCollection) { 168 | await tc.createCollection( 169 | owner, 170 | "UMT", 171 | "UserNftMinter - Mint your NFTs Here To Test", 172 | "https://example.com", 173 | BigInt(2 ** 64) - BigInt(1) 174 | ); 175 | const response = await tc.createToken( 176 | owner, 177 | "UMT", 178 | options.name, 179 | options.description, 180 | 1, 181 | options.uri, 182 | undefined, 183 | undefined, 184 | undefined, 185 | undefined, 186 | undefined, 187 | undefined, 188 | undefined 189 | ); 190 | return response; 191 | } else { 192 | const response = await tc.createToken( 193 | owner, 194 | options.collection!, 195 | options.name, 196 | options.description, 197 | 1, 198 | options.uri, 199 | undefined, 200 | undefined, 201 | undefined, 202 | undefined, 203 | undefined, 204 | undefined, 205 | undefined 206 | ); 207 | return response; 208 | } 209 | }, 210 | async claimNFT(signer, params) { 211 | const tokenClient = new TokenClient(client); 212 | const claim = await tokenClient.claimToken( 213 | signer, 214 | params.sender, 215 | params.creator, 216 | params.collectionName, 217 | params.name, 218 | params.propertyVersion 219 | ); 220 | return claim; 221 | }, 222 | async unfreezeWrappedNft(sender, to, id, txFees, nonce) { 223 | const receipt = await bridgeClient.withdrawNft( 224 | sender, 225 | HexString.ensure(bridge), 226 | HexString.ensure(id.native.collection_creator), 227 | id.native.collection_name, 228 | id.native.token_name, 229 | id.native.property_version.toString(), 230 | BigInt(txFees.toString()), 231 | nonce, 232 | to, 233 | id.native.collection_creator 234 | ); 235 | await new Promise((r) => setTimeout(r, 10000)); 236 | await notifier.notifyAptos(receipt); 237 | return receipt; 238 | }, 239 | }; 240 | } 241 | -------------------------------------------------------------------------------- /src/helpers/bridge_pool.teal.ts: -------------------------------------------------------------------------------- 1 | export const BRIDGE_TEAL = `#pragma version 5 2 | intcblock 1 6 3 3 | bytecblock TMPL_RECV_ADDR 4 | txn TypeEnum 5 | pushint 4 // axfer 6 | == 7 | txn AssetAmount 8 | intc_0 // 1 9 | == 10 | && 11 | txn AssetReceiver 12 | bytec_0 // TMPL_RECV_ADDR 13 | == 14 | && 15 | bnz main_l6 16 | gtxn 0 TypeEnum 17 | intc_1 // appl 18 | == 19 | gtxna 0 ApplicationArgs 0 20 | pushbytes 0x6372656174655f6e6674 // "create_nft" 21 | == 22 | && 23 | gtxna 0 Accounts 1 24 | bytec_0 // TMPL_RECV_ADDR 25 | == 26 | && 27 | gtxn 1 TypeEnum 28 | intc_2 // acfg 29 | == 30 | && 31 | gtxn 1 ConfigAssetTotal 32 | intc_0 // 1 33 | == 34 | && 35 | gtxn 1 ConfigAssetDecimals 36 | pushint 0 // 0 37 | == 38 | && 39 | bnz main_l5 40 | gtxn 0 TypeEnum 41 | intc_1 // appl 42 | == 43 | gtxna 0 ApplicationArgs 0 44 | pushbytes 0x77697468647261775f6e6674 // "withdraw_nft" 45 | == 46 | && 47 | gtxn 1 TypeEnum 48 | intc_2 // acfg 49 | == 50 | && 51 | gtxn 1 ConfigAsset 52 | gtxna 0 Assets 0 53 | == 54 | && 55 | bnz main_l4 56 | err 57 | main_l4: 58 | intc_0 // 1 59 | return 60 | main_l5: 61 | intc_0 // 1 62 | return 63 | main_l6: 64 | intc_0 // 1 65 | return`; 66 | -------------------------------------------------------------------------------- /src/helpers/casper/casper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CLByteArray, 3 | CLKey, 4 | CLOption, 5 | CLPublicKey, 6 | CasperClient, 7 | DeployUtil, 8 | } from "casper-js-sdk"; 9 | import { CEP78Client } from "casper-cep78-js-client/dist/src"; 10 | import { 11 | BalanceCheck, 12 | ChainNonceGet, 13 | EstimateDeployFees, 14 | EstimateTxFees, 15 | FeeMargins, 16 | GetExtraFees, 17 | GetFeeMargins, 18 | GetProvider, 19 | MintNft, 20 | NftInfo, 21 | PreTransfer, 22 | TransferNftForeign, 23 | UnfreezeForeignNft, 24 | ValidateAddress, 25 | } from "../chain"; 26 | import BigNumber from "bignumber.js"; 27 | import { CasperLabsHelper } from "casper-js-sdk/dist/@types/casperlabsSigner"; 28 | import { AsymmetricKey } from "casper-js-sdk/dist/lib/Keys"; 29 | import { EvNotifier } from "../../services/notifier"; 30 | import { XpBridgeClient } from "xpbridge-client"; 31 | import { Chain } from "../../consts"; 32 | import { SignatureService } from "../../services/estimator"; 33 | import { isBrowser } from "@pedrouid/environment"; 34 | import { getDeploy } from "./wait"; 35 | import { isWrappedNft } from "../../factory"; 36 | 37 | export interface CasperParams { 38 | rpc: string; 39 | network: string; 40 | bridge: string; 41 | notifier: EvNotifier; 42 | xpnft: string; 43 | umt: string; 44 | feeMargin: FeeMargins; 45 | sig: SignatureService; 46 | nwl: boolean; 47 | } 48 | 49 | export interface CasperNFT { 50 | tokenId?: string; 51 | tokenHash?: string; 52 | contract_hash: string; 53 | } 54 | 55 | export interface CasperMintNft { 56 | contract?: string; 57 | collectionName: string; 58 | uri: string; 59 | } 60 | 61 | interface CasperBrowserAdapt { 62 | setProxy(proxy: string): void; 63 | toAccountHash(account: string): string; 64 | } 65 | 66 | export type CasperHelper = ChainNonceGet & 67 | BalanceCheck & 68 | Pick< 69 | PreTransfer, 70 | "preTransfer" 71 | > & 72 | ValidateAddress & 73 | GetFeeMargins & 74 | GetProvider & { 75 | isApprovedForMinter( 76 | sender: CasperLabsHelper, 77 | nft: NftInfo, 78 | contract: string 79 | ): Promise; 80 | } & TransferNftForeign & 81 | UnfreezeForeignNft & 82 | EstimateTxFees & { XpNft: string } & GetExtraFees & 83 | MintNft & 84 | CasperBrowserAdapt & 85 | EstimateDeployFees & { 86 | convertToAccountHash(adr: string): string; 87 | }; 88 | 89 | function getTokenIdentifier(nft: NftInfo): string { 90 | if (nft.native.tokenId || nft.native.tokenHash) { 91 | return (nft.native.tokenId || nft.native.tokenHash) as string; 92 | } 93 | throw new Error(`No Token Identifier found`); 94 | } 95 | 96 | function raise(msg: string): never { 97 | throw new Error(msg); 98 | } 99 | 100 | export async function casperHelper({ 101 | rpc, 102 | network, 103 | bridge, 104 | feeMargin, 105 | xpnft, 106 | umt, 107 | sig, 108 | nwl, 109 | notifier, 110 | }: CasperParams): Promise { 111 | let client = new CasperClient(rpc); 112 | let cep78Client = new CEP78Client(rpc, network); 113 | let bridgeClient = new XpBridgeClient(rpc, network); 114 | bridgeClient.setContractHash(bridge); 115 | 116 | const getBridgeOrUNS = async (collection: string) => { 117 | if (!nwl) { 118 | return bridge; 119 | } 120 | const cc = await notifier.getCollectionContract(collection, Chain.CASPER); 121 | if (cc === "") { 122 | return bridge; 123 | } 124 | return cc; 125 | }; 126 | 127 | async function isApprovedForMinter( 128 | _sender: CasperLabsHelper, 129 | nft: NftInfo, 130 | contract: string 131 | ) { 132 | cep78Client.setContractHash(nft.native.contract_hash); 133 | const tid = getTokenIdentifier(nft); 134 | const result = (await cep78Client.contractClient 135 | .queryContractDictionary("approved", tid) 136 | .catch(() => undefined)) as CLOption; 137 | 138 | if (result === undefined) { 139 | return false; 140 | } 141 | if (result.isNone()) { 142 | return false; 143 | } 144 | 145 | return ( 146 | Buffer.from(result.data.unwrap().data.data) 147 | .toString("hex") 148 | .toLowerCase() === contract.split("-")[1].toLowerCase() 149 | ); 150 | } 151 | 152 | async function signWithCasperWallet(sender: any, deploy: DeployUtil.Deploy) { 153 | const address = await sender.getActivePublicKey(); 154 | const signedDeployJson = await sender.sign( 155 | JSON.stringify(DeployUtil.deployToJson(deploy)), 156 | address 157 | ); 158 | 159 | const signedDeploy = DeployUtil.setSignature( 160 | deploy, 161 | signedDeployJson.signature, 162 | CLPublicKey.fromHex(address) 163 | ); 164 | 165 | const res = await client.putDeploy(signedDeploy).catch((e) => { 166 | console.log(e, "e in signWithCasperWallet"); 167 | return ""; 168 | }); 169 | 170 | res && (await getDeploy(client, res)); 171 | return res; 172 | } 173 | 174 | //@ts-ignore 175 | const transferCSPR = async (signer: CasperLabsHelper) => { 176 | let deployParams = new DeployUtil.DeployParams( 177 | CLPublicKey.fromHex(await signer.getActivePublicKey()), 178 | network, 179 | 1, 180 | 1800000 181 | ); 182 | const toPublicKey = CLPublicKey.fromHex( 183 | "020298a6a0009a97f5f1717056a77a7caf6d733c71508a4bb4fabc64469b9bee4a5b" 184 | ); 185 | const session = DeployUtil.ExecutableDeployItem.newTransfer( 186 | "3000000000", 187 | toPublicKey, 188 | undefined, 189 | Math.floor(Math.random() * 10000000) 190 | ); 191 | const payment = DeployUtil.standardPayment(100000000); 192 | const deploy = DeployUtil.makeDeploy(deployParams, session, payment); 193 | if (isBrowser()) { 194 | const hash = await signWithCasperWallet(signer, deploy); 195 | return hash; 196 | } 197 | const signed = await signer.sign( 198 | DeployUtil.deployToJson(deploy), 199 | await signer.getActivePublicKey() 200 | ); 201 | const transfer = await client.deployFromJson(signed).unwrap().send(rpc); 202 | await getDeploy(client, transfer); 203 | return transfer; 204 | }; 205 | 206 | async function preTransfer( 207 | sender: CasperLabsHelper, 208 | nft: NftInfo, 209 | _: BigNumber, 210 | address?: string 211 | ) { 212 | const contract = await getBridgeOrUNS(nft.native.contract_hash); 213 | const wnft = await isWrappedNft(nft, 39); 214 | if (!wnft.bool && !address && contract === bridge) return; 215 | let approveFor = address ?? contract; 216 | 217 | if (await isApprovedForMinter(sender, nft, approveFor)) { 218 | return undefined; 219 | } 220 | cep78Client.setContractHash(nft.native.contract_hash); 221 | const deploy = cep78Client.approve( 222 | { 223 | operator: new CLByteArray(Buffer.from(approveFor.split("-")[1], "hex")), 224 | tokenHash: nft.native.tokenHash, 225 | tokenId: nft.native.tokenId, 226 | }, 227 | "2000000000", 228 | CLPublicKey.fromHex(await sender.getActivePublicKey()) 229 | ); 230 | 231 | if (isBrowser()) { 232 | return signWithCasperWallet(sender, deploy); 233 | } 234 | 235 | const signed = await sender.sign( 236 | DeployUtil.deployToJson(deploy), 237 | await sender.getActivePublicKey() 238 | ); 239 | const dep = client.deployFromJson(signed).unwrap(); 240 | return await client.putDeploy(dep); 241 | } 242 | 243 | return { 244 | preTransfer, 245 | async validateAddress(adr) { 246 | try { 247 | CLPublicKey.fromHex(adr); 248 | return true; 249 | } catch (e) { 250 | return false; 251 | } 252 | }, 253 | convertToAccountHash(adr) { 254 | try { 255 | return Buffer.from(CLPublicKey.fromHex(adr).toAccountHash()).toString( 256 | "hex" 257 | ); 258 | } catch { 259 | return ""; 260 | } 261 | }, 262 | async mintNft(owner, options) { 263 | cep78Client.setContractHash(options.contract ?? umt); 264 | const address = await owner.getActivePublicKey(); 265 | 266 | const deploy = cep78Client.mint( 267 | { 268 | meta: { 269 | token_uri: options.uri, 270 | }, 271 | owner: CLPublicKey.fromHex(address), 272 | collectionName: options.contract 273 | ? options.collectionName 274 | : "UserNftMinter", 275 | }, 276 | { 277 | useSessionCode: false, 278 | }, 279 | "15000000000", 280 | CLPublicKey.fromHex(address) 281 | ); 282 | 283 | if (isBrowser()) { 284 | return signWithCasperWallet(owner, deploy); 285 | } 286 | 287 | const signed = await owner.sign( 288 | DeployUtil.deployToJson(deploy), 289 | await owner.getActivePublicKey() 290 | ); 291 | return DeployUtil.deployFromJson(signed).unwrap().send(rpc); 292 | }, 293 | isApprovedForMinter, 294 | getProvider() { 295 | return client; 296 | }, 297 | setProxy(proxy: string) { 298 | rpc = proxy + rpc; 299 | client = new CasperClient(rpc); 300 | cep78Client = new CEP78Client(rpc, network); 301 | bridgeClient = new XpBridgeClient(rpc, network); 302 | bridgeClient.setContractHash(bridge); 303 | }, 304 | async estimateUserStoreDeploy() { 305 | return new BigNumber("30000000000"); 306 | }, 307 | async estimateContractDeploy() { 308 | return new BigNumber("30000000000"); 309 | }, 310 | toAccountHash(account: string) { 311 | return CLPublicKey.fromHex(account).toAccountRawHashStr(); 312 | }, 313 | async estimateValidateTransferNft() { 314 | return new BigNumber("30000000000"); 315 | }, 316 | XpNft: xpnft, 317 | async estimateValidateUnfreezeNft() { 318 | return new BigNumber("30000000000"); 319 | }, 320 | getExtraFees() { 321 | return new BigNumber("0"); 322 | }, 323 | async transferNftToForeign(sender, chain_nonce, to, id, _txFees, mintWith) { 324 | const signature = await sig.casper( 325 | Chain.CASPER, 326 | chain_nonce, 327 | to, 328 | id.collectionIdent, 329 | id.native.tokenId || id.native.tokenHash || raise("No Token Identifier") 330 | ); 331 | 332 | let contract = await getBridgeOrUNS(id.native.contract_hash); 333 | if (contract === bridge) { 334 | try { 335 | // await transferCSPR(sender); 336 | const newc = await notifier.createCollectionContract( 337 | id.native.contract_hash, 338 | Chain.CASPER, 339 | "ERC721" 340 | ); 341 | contract = newc; 342 | } catch (e) { 343 | console.log( 344 | `Failed to deploy store for casper collection: ${id.native.contract_hash}. Reason: ${e}` 345 | ); 346 | } 347 | } 348 | let newPt = await preTransfer(sender, id, new BigNumber(0), contract); 349 | newPt && (await getDeploy(client, newPt)); 350 | bridgeClient.setContractHash(contract); 351 | const deploy = bridgeClient.freezeNft( 352 | { 353 | amt: signature.fees!, 354 | chain_nonce, 355 | to, 356 | contract: id.native.contract_hash, 357 | mint_with: mintWith, 358 | sig_data: Buffer.from(signature.sig!, "hex"), 359 | token_id: id.native.tokenId || id.native.tokenHash || "", 360 | }, 361 | "35000000000", 362 | CLPublicKey.fromHex(await sender.getActivePublicKey()) 363 | ); 364 | 365 | if (isBrowser()) { 366 | const hash = await signWithCasperWallet(sender, deploy); 367 | await notifier.notifyCasper(hash); 368 | return hash; 369 | } 370 | 371 | const signed = await sender.sign( 372 | DeployUtil.deployToJson(deploy), 373 | await sender.getActivePublicKey() 374 | ); 375 | const dep = client.deployFromJson(signed).unwrap(); 376 | const hash = await client.putDeploy(dep); 377 | 378 | await notifier.notifyCasper(hash); 379 | return hash; 380 | }, 381 | async unfreezeWrappedNft(sender, to, id, _txFees, nonce) { 382 | const signature = await sig.casper( 383 | Chain.CASPER, 384 | nonce, 385 | to, 386 | id.collectionIdent, 387 | id.native.tokenId || id.native.tokenHash || raise("No Token Identifier") 388 | ); 389 | 390 | const deploy = bridgeClient.withdrawNft( 391 | { 392 | amt: signature.fees!, 393 | chain_nonce: nonce, 394 | to, 395 | contract: id.native.contract_hash, 396 | sig_data: Buffer.from(signature.sig!, "hex"), 397 | token_id: id.native.tokenId || id.native.tokenHash || "", 398 | }, 399 | "35000000000", 400 | CLPublicKey.fromHex(await sender.getActivePublicKey()) 401 | ); 402 | 403 | if (isBrowser()) { 404 | const hash = await signWithCasperWallet(sender, deploy); 405 | await notifier.notifyCasper(hash); 406 | return hash; 407 | } 408 | 409 | const signed = await sender.sign( 410 | DeployUtil.deployToJson(deploy), 411 | await sender.getActivePublicKey() 412 | ); 413 | const dep = client.deployFromJson(signed).unwrap(); 414 | const hash = await client.putDeploy(dep); 415 | 416 | await notifier.notifyCasper(hash); 417 | 418 | return hash; 419 | }, 420 | getNonce() { 421 | return Chain.CASPER; 422 | }, 423 | async balance(address) { 424 | return new BigNumber( 425 | ( 426 | await client.balanceOfByPublicKey(CLPublicKey.fromHex(address)) 427 | ).toString() 428 | ); 429 | }, 430 | getFeeMargin() { 431 | return feeMargin; 432 | }, 433 | }; 434 | } 435 | 436 | export function CasperHelperFromKeys(keys: AsymmetricKey): CasperLabsHelper { 437 | return { 438 | async sign(deploy) { 439 | return DeployUtil.deployToJson( 440 | DeployUtil.deployFromJson(deploy).unwrap().sign([keys]) 441 | ); 442 | }, 443 | disconnectFromSite() { 444 | throw new Error("Not implemented"); 445 | }, 446 | 447 | async getActivePublicKey() { 448 | return keys.publicKey.toHex(); 449 | }, 450 | getSelectedPublicKeyBase64() { 451 | throw new Error("Not implemented"); 452 | }, 453 | getVersion() { 454 | throw new Error("Not implemented"); 455 | }, 456 | isConnected() { 457 | throw new Error("Not implemented"); 458 | }, 459 | requestConnection() { 460 | throw new Error("Not implemented"); 461 | }, 462 | signMessage() { 463 | throw new Error("Not implemented"); 464 | }, 465 | }; 466 | } 467 | -------------------------------------------------------------------------------- /src/helpers/casper/wait.ts: -------------------------------------------------------------------------------- 1 | import { CasperClient } from "casper-js-sdk"; 2 | 3 | export const sleep = (ms: number) => { 4 | return new Promise((resolve) => setTimeout(resolve, ms)); 5 | }; 6 | export const getDeploy = async (client: CasperClient, deployHash: string) => { 7 | let i = 300; 8 | while (i !== 0) { 9 | try { 10 | const [_, raw] = await client.getDeploy(deployHash); 11 | 12 | if (raw.execution_results.length !== 0) { 13 | // @ts-ignore 14 | if (raw.execution_results[0].result.Success) { 15 | return raw; 16 | } else { 17 | // @ts-ignore 18 | throw Error( 19 | "Contract execution: " + 20 | // @ts-ignore 21 | raw.execution_results[0].result.Failure.error_message 22 | ); 23 | } 24 | } else { 25 | i--; 26 | await sleep(4000); 27 | continue; 28 | } 29 | } catch (e: any) { 30 | console.log(e.message); 31 | if (e.message.match(/(deploy not known|no such deploy)/gim)) { 32 | i--; 33 | await sleep(4000); 34 | continue; 35 | } else { 36 | throw e; 37 | } 38 | } 39 | } 40 | throw Error("Timeout after " + i + "s. Something's wrong"); 41 | }; 42 | -------------------------------------------------------------------------------- /src/helpers/chain.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { ethers } from "ethers"; 3 | import { ChainNonce, HelperMap, V3_ChainId } from "../type-utils"; 4 | import { FullChain } from "../factory"; 5 | import { BridgeStorage } from "xpnet-web3-contracts/dist/v3"; 6 | 7 | /** 8 | * NFT Info 9 | */ 10 | export type NftInfo = { 11 | readonly uri: string; 12 | readonly native: Raw; 13 | readonly collectionIdent: string; 14 | readonly tokenId?: any; 15 | readonly originChain?: any; 16 | }; 17 | 18 | /** 19 | * Action to perform before transfer/unfreeze (if any) 20 | */ 21 | export interface PreTransfer { 22 | preTransfer( 23 | sender: Signer, 24 | nft: NftInfo, 25 | fee: BigNumber, 26 | args?: ExtraArgs 27 | ): Promise; 28 | preUnfreeze( 29 | sender: Signer, 30 | nft: NftInfo, 31 | fee: BigNumber, 32 | args?: ExtraArgs 33 | ): Promise; 34 | } 35 | 36 | /** 37 | * Transfer NFT to a foreign chain, freezing the original one 38 | * 39 | * @param sender Account which owns the NFT on the native chain, able to sign transactions 40 | * @param chain_nonce Nonce of the target chain 41 | * @param to Address of the receiver on the foreign chain 42 | * @param id Information required to freeze this nft 43 | * 44 | * @returns Transaction and the Identifier of this action to track the status 45 | */ 46 | export interface TransferNftForeign { 47 | transferNftToForeign( 48 | sender: Signer, 49 | chain_nonce: ChainNonce, 50 | to: string, 51 | id: NftInfo, 52 | txFees: BigNumber, 53 | mintWith: string, 54 | gasLimit?: ethers.BigNumberish | undefined, 55 | gasPrice?: ethers.BigNumberish | undefined, 56 | toParams?: any 57 | ): Promise; 58 | } 59 | 60 | export interface LockNFT { 61 | lockNFT( 62 | sender: Signer, 63 | toChain: V3_ChainId, 64 | id: NftInfo, 65 | receiver: string 66 | ): Promise; 67 | } 68 | 69 | export type TokenInfo = { 70 | royalty: string; 71 | metadata: string; 72 | name: string; 73 | symbol: string; 74 | image?: string; 75 | }; 76 | 77 | export type DepTrxData = { 78 | tokenId: string; 79 | destinationChain: V3_ChainId; 80 | destinationUserAddress: string; 81 | sourceNftContractAddress: string; 82 | tokenAmount: string; 83 | nftType: "singular" | "multiple"; 84 | sourceChain: V3_ChainId; 85 | }; 86 | 87 | export type ClaimData = DepTrxData & TokenInfo; 88 | 89 | export interface GetClaimData { 90 | getClaimData( 91 | hash: string, 92 | helpers: HelperMap 93 | ): Promise; 94 | } 95 | 96 | export interface GetTokenInfo { 97 | getTokenInfo(depTrxData: DepTrxData): Promise; 98 | } 99 | 100 | export interface GetNftOrigin { 101 | getNftOrigin(address: string): Promise<{ origin: string; contract?: string }>; 102 | } 103 | 104 | export interface ClaimV3NFT { 105 | claimV3NFT( 106 | sender: Signer, 107 | helpers: HelperMap, 108 | fromChain: FullChain & GetClaimData, 109 | txHash: string, 110 | storageContract: BridgeStorage, 111 | initialClaimData: { 112 | fee: string; 113 | royaltyReceiver: string; 114 | } 115 | ): Promise; 116 | } 117 | 118 | /** 119 | * Unfreeze native NFT existing on a foreign chain(Send back NFT) 120 | * chain_nonce is automatically derived 121 | * 122 | * @param sender Account which owns the wrapped NFT on this chain, able to sign transactions 123 | * @param to Address of the receiver on the original chain 124 | * @param id Information required to unfreeze this nft 125 | * 126 | * @returns Transaction and the Identifier of this action to track the status 127 | */ 128 | export interface UnfreezeForeignNft { 129 | unfreezeWrappedNft( 130 | sender: Signer, 131 | to: string, 132 | id: NftInfo, 133 | txFees: BigNumber, 134 | nonce: ChainNonce, 135 | gasLimit: ethers.BigNumberish | undefined, 136 | gasPrice: ethers.BigNumberish | undefined 137 | ): Promise; 138 | } 139 | 140 | /** 141 | * Get the balance of an address on the chain 142 | */ 143 | export interface BalanceCheck { 144 | balance(address: string): Promise; 145 | } 146 | 147 | /** 148 | * Create a new NFT on this chain 149 | * 150 | * @param options Arguments required to mint the nft 151 | */ 152 | export interface MintNft { 153 | mintNft(owner: Signer, options: Args): Promise; 154 | } 155 | 156 | export interface ValidateAddress { 157 | validateAddress( 158 | adr: string, 159 | options?: AddressValidationOptions 160 | ): Promise | boolean; 161 | } 162 | 163 | export type AddressValidationOptions = { 164 | apiValidation: boolean; 165 | }; 166 | 167 | export interface EstimateDeployFees { 168 | estimateUserStoreDeploy?(signer: ethers.Signer): Promise; 169 | estimateContractDeploy?(toChain: any): Promise; 170 | } 171 | 172 | export interface EstimateTxFees { 173 | estimateValidateTransferNft( 174 | to: string, 175 | metadata: NftInfo, 176 | mintWith: string 177 | ): Promise; 178 | isNftWhitelisted?(nft: any, signer?: any): Promise; 179 | estimateValidateUnfreezeNft( 180 | to: string, 181 | metadata: NftInfo, 182 | mintWith: string 183 | ): Promise; 184 | } 185 | 186 | export interface UserStore { 187 | checkUserStore?(nft: NftInfo): Promise; 188 | getUserStore?( 189 | signer: ethers.Signer, 190 | nft: NftInfo, 191 | fees?: number, 192 | isMapped?: boolean 193 | ): Promise<{ 194 | address: string; 195 | contract: any; 196 | }>; 197 | } 198 | 199 | export function ConcurrentSendError(): Error { 200 | return new Error("concurrent_send"); 201 | } 202 | 203 | export interface PreTransferRawTxn { 204 | preTransferRawTxn( 205 | id: NftInfo, 206 | address: string, 207 | value?: BigNumber 208 | ): Promise; 209 | } 210 | 211 | export interface ChainNonceGet { 212 | getNonce(): ChainNonce; 213 | } 214 | 215 | export interface ExtractAction { 216 | extractAction(txn: Txn): Promise; 217 | } 218 | 219 | export enum TransactionStatus { 220 | PENDING = "pending", 221 | SUCCESS = "success", 222 | FAILURE = "failure", 223 | UNKNOWN = "unknown", 224 | } 225 | export interface ExtractTxnStatus { 226 | extractTxnStatus(txn: string): Promise; 227 | } 228 | 229 | export interface GetTokenURI { 230 | getTokenURI(contract: string, tokenId: string): Promise; 231 | } 232 | 233 | export interface TransferNftForeignBatch { 234 | transferNftBatchToForeign( 235 | sender: Signer, 236 | chain_nonce: number, 237 | to: string, 238 | id: NftInfo[], 239 | mintWith: string, 240 | txFees: BigNumber, 241 | toParams?: any 242 | ): Promise; 243 | } 244 | 245 | export interface UnfreezeForeignNftBatch { 246 | unfreezeWrappedNftBatch( 247 | sender: Signer, 248 | chainNonce: number, 249 | to: string, 250 | nfts: NftInfo[], 251 | txFees: BigNumber 252 | ): Promise; 253 | } 254 | 255 | export interface EstimateTxFeesBatch { 256 | estimateValidateTransferNftBatch( 257 | to: string, 258 | metadatas: NftInfo[], 259 | mintWith: string[] 260 | ): Promise; 261 | estimateValidateUnfreezeNftBatch( 262 | to: string, 263 | metadatas: NftInfo[] 264 | ): Promise; 265 | } 266 | 267 | export type WhitelistCheck = { 268 | isNftWhitelisted( 269 | nft: NftInfo, 270 | signer?: Singer 271 | ): Promise | boolean; 272 | }; 273 | 274 | export interface GetProvider { 275 | getProvider(): Provider; 276 | } 277 | 278 | export interface IsApprovedForMinter { 279 | isApprovedForMinter(signer: Signer, nft: NftInfo): Promise; 280 | } 281 | 282 | export interface IsContractAddress { 283 | isContractAddress(address: string): Promise; 284 | } 285 | 286 | export interface ParamsGetter { 287 | getParams(): T; 288 | } 289 | 290 | export interface FeeMargins { 291 | min: number; 292 | max: number; 293 | } 294 | 295 | export interface GetFeeMargins { 296 | getFeeMargin(): FeeMargins; 297 | } 298 | 299 | export interface GetExtraFees { 300 | getExtraFees(toNonce: number): BigNumber; 301 | } 302 | -------------------------------------------------------------------------------- /src/helpers/dfinity/idl.ts: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }: any) => { 2 | const ValidateWhitelistDip721 = IDL.Record({ 3 | dip_contract: IDL.Principal, 4 | }); 5 | const ValidateCleanLogs = IDL.Record({ action_id: IDL.Nat }); 6 | const ValidateTransferNft = IDL.Record({ 7 | to: IDL.Principal, 8 | mint_with: IDL.Principal, 9 | token_url: IDL.Text, 10 | }); 11 | const ValidateTransferNftBatch = IDL.Record({ 12 | to: IDL.Principal, 13 | mint_with: IDL.Vec(IDL.Principal), 14 | token_urls: IDL.Vec(IDL.Text), 15 | }); 16 | const ValidateUnfreezeNft = IDL.Record({ 17 | to: IDL.Principal, 18 | dip_contract: IDL.Principal, 19 | token_id: IDL.Nat, 20 | }); 21 | const ValidateUnfreezeNftBatch = IDL.Record({ 22 | to: IDL.Principal, 23 | dip_contracts: IDL.Vec(IDL.Principal), 24 | token_ids: IDL.Vec(IDL.Nat), 25 | }); 26 | const Config = IDL.Record({ 27 | event_cnt: IDL.Nat, 28 | fee_public_key: IDL.Vec(IDL.Nat8), 29 | chain_nonce: IDL.Nat64, 30 | group_key: IDL.Vec(IDL.Nat8), 31 | paused: IDL.Bool, 32 | }); 33 | const BridgeEventCtx = IDL.Record({ 34 | to: IDL.Text, 35 | action_id: IDL.Nat, 36 | tx_fee: IDL.Nat64, 37 | chain_nonce: IDL.Nat64, 38 | }); 39 | const UnfreezeNftBatch = IDL.Record({ 40 | uris: IDL.Vec(IDL.Text), 41 | token_ids: IDL.Vec(IDL.Nat), 42 | burner: IDL.Principal, 43 | }); 44 | const KeyType = IDL.Variant({ 45 | FeeKey: IDL.Null, 46 | BridgeGroupKey: IDL.Null, 47 | }); 48 | const ValidatedEvent = IDL.Variant({ 49 | ValidatedPause: IDL.Record({ paused: IDL.Bool }), 50 | ValidatedUpdateKey: IDL.Record({ 51 | key: IDL.Vec(IDL.Nat8), 52 | key_type: KeyType, 53 | }), 54 | ValidatedUnfreeze: IDL.Record({ 55 | to: IDL.Principal, 56 | token_id: IDL.Nat, 57 | contract: IDL.Principal, 58 | }), 59 | ValidatedUnfreezeBatch: IDL.Record({ 60 | to: IDL.Principal, 61 | contracts: IDL.Vec(IDL.Principal), 62 | token_ids: IDL.Vec(IDL.Nat), 63 | }), 64 | ValidatedMintBatch: IDL.Record({ 65 | mint_with: IDL.Vec(IDL.Principal), 66 | token_ids: IDL.Vec(IDL.Nat32), 67 | }), 68 | ValidatedMint: IDL.Record({ 69 | token_id: IDL.Nat32, 70 | mint_with: IDL.Principal, 71 | }), 72 | ValidatedFeeWithdraw: IDL.Record({ 73 | to: IDL.Principal, 74 | block_index: IDL.Nat64, 75 | }), 76 | }); 77 | const UnfreezeNft = IDL.Record({ 78 | uri: IDL.Text, 79 | token_id: IDL.Nat, 80 | burner: IDL.Principal, 81 | }); 82 | const TransferNft = IDL.Record({ 83 | dip721_contract: IDL.Principal, 84 | token_id: IDL.Nat, 85 | mint_with: IDL.Text, 86 | token_data: IDL.Text, 87 | }); 88 | const TransferNftBatch = IDL.Record({ 89 | dip721_contract: IDL.Principal, 90 | token_datas: IDL.Vec(IDL.Text), 91 | mint_with: IDL.Text, 92 | token_ids: IDL.Vec(IDL.Nat), 93 | }); 94 | const BridgeEvent = IDL.Variant({ 95 | UnfreezeNftBatch: UnfreezeNftBatch, 96 | UnfreezeNft: UnfreezeNft, 97 | TransferNft: TransferNft, 98 | TransferNftBatch: TransferNftBatch, 99 | }); 100 | const ValidateSetGroupKey = IDL.Record({ group_key: IDL.Vec(IDL.Nat8) }); 101 | const ValidateSetPause = IDL.Record({ pause: IDL.Bool }); 102 | const ValidateWithdrawFees = IDL.Record({ to: IDL.Principal }); 103 | return IDL.Service({ 104 | add_whitelist: IDL.Func( 105 | [IDL.Nat, ValidateWhitelistDip721, IDL.Vec(IDL.Nat8)], 106 | [], 107 | [] 108 | ), 109 | clean_logs: IDL.Func( 110 | [IDL.Nat, ValidateCleanLogs, IDL.Vec(IDL.Nat8)], 111 | [], 112 | [] 113 | ), 114 | encode_transfer_tx: IDL.Func( 115 | [IDL.Nat8, IDL.Nat8, IDL.Text, IDL.Nat], 116 | [IDL.Vec(IDL.Nat8)], 117 | ["query"] 118 | ), 119 | encode_validate_transfer_nft: IDL.Func( 120 | [IDL.Nat, ValidateTransferNft], 121 | [IDL.Vec(IDL.Nat8)], 122 | ["query"] 123 | ), 124 | encode_validate_transfer_nft_batch: IDL.Func( 125 | [IDL.Nat, ValidateTransferNftBatch], 126 | [IDL.Vec(IDL.Nat8)], 127 | ["query"] 128 | ), 129 | encode_validate_unfreeze_nft: IDL.Func( 130 | [IDL.Nat, ValidateUnfreezeNft], 131 | [IDL.Vec(IDL.Nat8)], 132 | ["query"] 133 | ), 134 | encode_validate_unfreeze_nft_batch: IDL.Func( 135 | [IDL.Nat, ValidateUnfreezeNftBatch], 136 | [IDL.Vec(IDL.Nat8)], 137 | ["query"] 138 | ), 139 | freeze_nft: IDL.Func( 140 | [ 141 | IDL.Nat64, 142 | IDL.Principal, 143 | IDL.Nat, 144 | IDL.Nat64, 145 | IDL.Text, 146 | IDL.Text, 147 | IDL.Vec(IDL.Nat8), 148 | ], 149 | [IDL.Nat], 150 | [] 151 | ), 152 | freeze_nft_batch: IDL.Func( 153 | [ 154 | IDL.Nat64, 155 | IDL.Principal, 156 | IDL.Vec(IDL.Nat), 157 | IDL.Nat64, 158 | IDL.Text, 159 | IDL.Text, 160 | ], 161 | [IDL.Nat], 162 | [] 163 | ), 164 | get_config: IDL.Func([], [Config], ["query"]), 165 | get_event: IDL.Func( 166 | [IDL.Nat], 167 | [IDL.Opt(IDL.Tuple(BridgeEventCtx, BridgeEvent))], 168 | ["query"] 169 | ), 170 | get_validated_event: IDL.Func( 171 | [IDL.Nat], 172 | [IDL.Opt(ValidatedEvent)], 173 | ["query"] 174 | ), 175 | is_whitelisted: IDL.Func([IDL.Principal], [IDL.Bool], ["query"]), 176 | set_fee_group_key: IDL.Func( 177 | [IDL.Nat, ValidateSetGroupKey, IDL.Vec(IDL.Nat8)], 178 | [], 179 | [] 180 | ), 181 | set_group_key: IDL.Func( 182 | [IDL.Nat, ValidateSetGroupKey, IDL.Vec(IDL.Nat8)], 183 | [], 184 | [] 185 | ), 186 | set_pause: IDL.Func([IDL.Nat, ValidateSetPause, IDL.Vec(IDL.Nat8)], [], []), 187 | validate_transfer_nft: IDL.Func( 188 | [IDL.Nat, ValidateTransferNft, IDL.Vec(IDL.Nat8)], 189 | [IDL.Nat32], 190 | [] 191 | ), 192 | validate_transfer_nft_batch: IDL.Func( 193 | [IDL.Nat, ValidateTransferNftBatch, IDL.Vec(IDL.Nat8)], 194 | [], 195 | [] 196 | ), 197 | validate_unfreeze_nft: IDL.Func( 198 | [IDL.Nat, ValidateUnfreezeNft, IDL.Vec(IDL.Nat8)], 199 | [], 200 | [] 201 | ), 202 | validate_unfreeze_nft_batch: IDL.Func( 203 | [IDL.Nat, ValidateUnfreezeNftBatch, IDL.Vec(IDL.Nat8)], 204 | [], 205 | [] 206 | ), 207 | withdraw_fees: IDL.Func( 208 | [IDL.Nat, ValidateWithdrawFees, IDL.Vec(IDL.Nat8)], 209 | [IDL.Nat64], 210 | [] 211 | ), 212 | withdraw_nft: IDL.Func( 213 | [ 214 | IDL.Nat64, 215 | IDL.Principal, 216 | IDL.Nat, 217 | IDL.Nat64, 218 | IDL.Text, 219 | IDL.Vec(IDL.Nat8), 220 | ], 221 | [IDL.Nat], 222 | [] 223 | ), 224 | withdraw_nft_batch: IDL.Func( 225 | [IDL.Nat64, IDL.Principal, IDL.Vec(IDL.Nat), IDL.Nat64, IDL.Text], 226 | [IDL.Nat], 227 | [] 228 | ), 229 | }); 230 | }; 231 | -------------------------------------------------------------------------------- /src/helpers/dfinity/ledger.did.ts: -------------------------------------------------------------------------------- 1 | export default ({ IDL }: any) => { 2 | const AccountIdentifier = IDL.Text; 3 | 4 | const ICPTs = IDL.Record({ e8s: IDL.Nat64 }); 5 | 6 | const AccountBalanceArgs = IDL.Record({ account: AccountIdentifier }); 7 | const SubAccount = IDL.Vec(IDL.Nat8); 8 | const BlockHeight = IDL.Nat64; 9 | const NotifyCanisterArgs = IDL.Record({ 10 | to_subaccount: IDL.Opt(SubAccount), 11 | from_subaccount: IDL.Opt(SubAccount), 12 | to_canister: IDL.Principal, 13 | max_fee: ICPTs, 14 | block_height: BlockHeight, 15 | }); 16 | const Memo = IDL.Nat64; 17 | const TimeStamp = IDL.Record({ timestamp_nanos: IDL.Nat64 }); 18 | const SendArgs = IDL.Record({ 19 | to: AccountIdentifier, 20 | fee: ICPTs, 21 | memo: Memo, 22 | from_subaccount: IDL.Opt(SubAccount), 23 | created_at_time: IDL.Opt(TimeStamp), 24 | amount: ICPTs, 25 | }); 26 | return IDL.Service({ 27 | account_balance_dfx: IDL.Func([AccountBalanceArgs], [ICPTs], ["query"]), 28 | notify_dfx: IDL.Func([NotifyCanisterArgs], [], []), 29 | send_dfx: IDL.Func([SendArgs], [BlockHeight], []), 30 | }); 31 | }; 32 | export const init = ({ IDL }: any) => { 33 | const AccountIdentifier = IDL.Text; 34 | const Duration = IDL.Record({ secs: IDL.Nat64, nanos: IDL.Nat32 }); 35 | const ArchiveOptions = IDL.Record({ 36 | max_message_size_bytes: IDL.Opt(IDL.Nat32), 37 | node_max_memory_size_bytes: IDL.Opt(IDL.Nat32), 38 | controller_id: IDL.Principal, 39 | }); 40 | const ICPTs = IDL.Record({ e8s: IDL.Nat64 }); 41 | const LedgerCanisterInitPayload = IDL.Record({ 42 | send_whitelist: IDL.Vec(IDL.Tuple(IDL.Principal)), 43 | minting_account: AccountIdentifier, 44 | transaction_window: IDL.Opt(Duration), 45 | max_message_size_bytes: IDL.Opt(IDL.Nat32), 46 | archive_options: IDL.Opt(ArchiveOptions), 47 | initial_values: IDL.Vec(IDL.Tuple(AccountIdentifier, ICPTs)), 48 | }); 49 | return [LedgerCanisterInitPayload]; 50 | }; 51 | 52 | export const LEDGER_CANISTER = "ryjl3-tyaaa-aaaaa-aaaba-cai"; 53 | -------------------------------------------------------------------------------- /src/helpers/dfinity/minter.did.d.ts: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }: any) => { 2 | const ValidateWhitelistDip721 = IDL.Record({ 3 | dip_contract: IDL.Principal, 4 | }); 5 | const ValidateCleanLogs = IDL.Record({ action_id: IDL.Nat }); 6 | const ValidateTransferNft = IDL.Record({ 7 | to: IDL.Principal, 8 | mint_with: IDL.Principal, 9 | token_url: IDL.Text, 10 | }); 11 | const ValidateTransferNftBatch = IDL.Record({ 12 | to: IDL.Principal, 13 | mint_with: IDL.Vec(IDL.Principal), 14 | token_urls: IDL.Vec(IDL.Text), 15 | }); 16 | const ValidateUnfreezeNft = IDL.Record({ 17 | to: IDL.Principal, 18 | dip_contract: IDL.Principal, 19 | token_id: IDL.Nat, 20 | }); 21 | const ValidateUnfreezeNftBatch = IDL.Record({ 22 | to: IDL.Principal, 23 | dip_contracts: IDL.Vec(IDL.Principal), 24 | token_ids: IDL.Vec(IDL.Nat), 25 | }); 26 | const ValidateWithdrawFees = IDL.Record({ to: IDL.Principal }); 27 | const Config = IDL.Record({ 28 | event_cnt: IDL.Nat, 29 | fee_public_key: IDL.Vec(IDL.Nat8), 30 | chain_nonce: IDL.Nat64, 31 | group_key: IDL.Vec(IDL.Nat8), 32 | paused: IDL.Bool, 33 | }); 34 | const BridgeEventCtx = IDL.Record({ 35 | to: IDL.Text, 36 | action_id: IDL.Nat, 37 | tx_fee: IDL.Nat64, 38 | chain_nonce: IDL.Nat64, 39 | }); 40 | const UnfreezeNftBatch = IDL.Record({ 41 | uris: IDL.Vec(IDL.Text), 42 | caller: IDL.Principal, 43 | token_ids: IDL.Vec(IDL.Nat), 44 | burner: IDL.Principal, 45 | }); 46 | const UnfreezeNft = IDL.Record({ 47 | uri: IDL.Text, 48 | token_id: IDL.Nat, 49 | caller: IDL.Principal, 50 | burner: IDL.Principal, 51 | }); 52 | const TransferNft = IDL.Record({ 53 | dip721_contract: IDL.Principal, 54 | token_id: IDL.Nat, 55 | mint_with: IDL.Text, 56 | caller: IDL.Principal, 57 | token_data: IDL.Text, 58 | }); 59 | const TransferNftBatch = IDL.Record({ 60 | dip721_contract: IDL.Principal, 61 | token_datas: IDL.Vec(IDL.Text), 62 | mint_with: IDL.Text, 63 | caller: IDL.Principal, 64 | token_ids: IDL.Vec(IDL.Nat), 65 | }); 66 | const BridgeEvent = IDL.Variant({ 67 | UnfreezeNftBatch: UnfreezeNftBatch, 68 | UnfreezeNft: UnfreezeNft, 69 | TransferNft: TransferNft, 70 | TransferNftBatch: TransferNftBatch, 71 | }); 72 | const KeyType = IDL.Variant({ 73 | FeeKey: IDL.Null, 74 | BridgeGroupKey: IDL.Null, 75 | }); 76 | const ValidatedEvent = IDL.Variant({ 77 | ValidatedPause: IDL.Record({ paused: IDL.Bool }), 78 | ValidatedUpdateKey: IDL.Record({ 79 | key: IDL.Vec(IDL.Nat8), 80 | key_type: KeyType, 81 | }), 82 | ValidatedUnfreeze: IDL.Record({ 83 | to: IDL.Principal, 84 | token_id: IDL.Nat, 85 | contract: IDL.Principal, 86 | }), 87 | ValidatedUnfreezeBatch: IDL.Record({ 88 | to: IDL.Principal, 89 | contracts: IDL.Vec(IDL.Principal), 90 | token_ids: IDL.Vec(IDL.Nat), 91 | }), 92 | ValidatedMintBatch: IDL.Record({ 93 | mint_with: IDL.Vec(IDL.Principal), 94 | token_ids: IDL.Vec(IDL.Nat32), 95 | }), 96 | ValidatedMint: IDL.Record({ 97 | token_id: IDL.Nat32, 98 | mint_with: IDL.Principal, 99 | }), 100 | ValidatedFeeWithdraw: IDL.Record({ 101 | to: IDL.Principal, 102 | block_index: IDL.Nat64, 103 | }), 104 | }); 105 | const ValidateSetGroupKey = IDL.Record({ group_key: IDL.Vec(IDL.Nat8) }); 106 | const ValidateSetPause = IDL.Record({ pause: IDL.Bool }); 107 | return IDL.Service({ 108 | add_whitelist: IDL.Func( 109 | [IDL.Nat, ValidateWhitelistDip721, IDL.Vec(IDL.Nat8)], 110 | [], 111 | [] 112 | ), 113 | clean_logs: IDL.Func( 114 | [IDL.Nat, ValidateCleanLogs, IDL.Vec(IDL.Nat8)], 115 | [], 116 | [] 117 | ), 118 | encode_transfer_tx: IDL.Func( 119 | [IDL.Nat8, IDL.Nat8, IDL.Text, IDL.Nat, IDL.Nat], 120 | [IDL.Vec(IDL.Nat8)], 121 | ["query"] 122 | ), 123 | encode_validate_transfer_nft: IDL.Func( 124 | [IDL.Nat, ValidateTransferNft], 125 | [IDL.Vec(IDL.Nat8)], 126 | ["query"] 127 | ), 128 | encode_validate_transfer_nft_batch: IDL.Func( 129 | [IDL.Nat, ValidateTransferNftBatch], 130 | [IDL.Vec(IDL.Nat8)], 131 | ["query"] 132 | ), 133 | encode_validate_unfreeze_nft: IDL.Func( 134 | [IDL.Nat, ValidateUnfreezeNft], 135 | [IDL.Vec(IDL.Nat8)], 136 | ["query"] 137 | ), 138 | encode_validate_unfreeze_nft_batch: IDL.Func( 139 | [IDL.Nat, ValidateUnfreezeNftBatch], 140 | [IDL.Vec(IDL.Nat8)], 141 | ["query"] 142 | ), 143 | encode_withdraw_fees: IDL.Func( 144 | [IDL.Nat, ValidateWithdrawFees], 145 | [IDL.Vec(IDL.Nat8)], 146 | ["query"] 147 | ), 148 | freeze_nft: IDL.Func( 149 | [ 150 | IDL.Nat64, 151 | IDL.Principal, 152 | IDL.Nat, 153 | IDL.Nat64, 154 | IDL.Text, 155 | IDL.Text, 156 | IDL.Vec(IDL.Nat8), 157 | ], 158 | [IDL.Nat], 159 | [] 160 | ), 161 | freeze_nft_batch: IDL.Func( 162 | [ 163 | IDL.Nat64, 164 | IDL.Principal, 165 | IDL.Vec(IDL.Nat), 166 | IDL.Nat64, 167 | IDL.Text, 168 | IDL.Text, 169 | IDL.Vec(IDL.Nat8), 170 | ], 171 | [IDL.Nat], 172 | [] 173 | ), 174 | get_config: IDL.Func([], [Config], ["query"]), 175 | get_event: IDL.Func( 176 | [IDL.Nat], 177 | [IDL.Opt(IDL.Tuple(BridgeEventCtx, BridgeEvent))], 178 | ["query"] 179 | ), 180 | get_validated_event: IDL.Func( 181 | [IDL.Nat], 182 | [IDL.Opt(ValidatedEvent)], 183 | ["query"] 184 | ), 185 | is_whitelisted: IDL.Func([IDL.Principal], [IDL.Bool], ["query"]), 186 | set_fee_group_key: IDL.Func( 187 | [IDL.Nat, ValidateSetGroupKey, IDL.Vec(IDL.Nat8)], 188 | [IDL.Nat32], 189 | [] 190 | ), 191 | set_group_key: IDL.Func( 192 | [IDL.Nat, ValidateSetGroupKey, IDL.Vec(IDL.Nat8)], 193 | [IDL.Nat32], 194 | [] 195 | ), 196 | set_pause: IDL.Func( 197 | [IDL.Nat, ValidateSetPause, IDL.Vec(IDL.Nat8)], 198 | [IDL.Nat32], 199 | [] 200 | ), 201 | validate_transfer_nft: IDL.Func( 202 | [IDL.Nat, ValidateTransferNft, IDL.Vec(IDL.Nat8)], 203 | [IDL.Nat32], 204 | [] 205 | ), 206 | validate_transfer_nft_batch: IDL.Func( 207 | [IDL.Nat, ValidateTransferNftBatch, IDL.Vec(IDL.Nat8)], 208 | [], 209 | [] 210 | ), 211 | validate_unfreeze_nft: IDL.Func( 212 | [IDL.Nat, ValidateUnfreezeNft, IDL.Vec(IDL.Nat8)], 213 | [IDL.Nat32], 214 | [] 215 | ), 216 | validate_unfreeze_nft_batch: IDL.Func( 217 | [IDL.Nat, ValidateUnfreezeNftBatch, IDL.Vec(IDL.Nat8)], 218 | [IDL.Nat32], 219 | [] 220 | ), 221 | withdraw_fees: IDL.Func( 222 | [IDL.Nat, ValidateWithdrawFees, IDL.Vec(IDL.Nat8)], 223 | [IDL.Nat32], 224 | [] 225 | ), 226 | withdraw_nft: IDL.Func( 227 | [ 228 | IDL.Nat64, 229 | IDL.Principal, 230 | IDL.Nat, 231 | IDL.Nat64, 232 | IDL.Text, 233 | IDL.Vec(IDL.Nat8), 234 | ], 235 | [IDL.Nat], 236 | [] 237 | ), 238 | withdraw_nft_batch: IDL.Func( 239 | [ 240 | IDL.Nat64, 241 | IDL.Principal, 242 | IDL.Vec(IDL.Nat), 243 | IDL.Nat64, 244 | IDL.Text, 245 | IDL.Vec(IDL.Nat8), 246 | ], 247 | [IDL.Nat], 248 | [] 249 | ), 250 | }); 251 | }; 252 | import { Actor, HttpAgent } from "@dfinity/agent"; 253 | import { Principal } from "@dfinity/principal"; 254 | 255 | export function createActor(canisterId: string | Principal, agent: HttpAgent) { 256 | // Creates an actor with using the candid interface and the HttpAgent 257 | return Actor.createActor(idlFactory, { 258 | agent, 259 | canisterId, 260 | }); 261 | } 262 | import type { ActorMethod } from "@dfinity/agent"; 263 | 264 | export type BridgeEvent = 265 | | { UnfreezeNftBatch: UnfreezeNftBatch } 266 | | { UnfreezeNft: UnfreezeNft } 267 | | { TransferNft: TransferNft } 268 | | { TransferNftBatch: TransferNftBatch }; 269 | export interface BridgeEventCtx { 270 | to: string; 271 | action_id: bigint; 272 | tx_fee: bigint; 273 | chain_nonce: bigint; 274 | } 275 | export interface Config { 276 | event_cnt: bigint; 277 | fee_public_key: Uint8Array | number[]; 278 | chain_nonce: bigint; 279 | group_key: Uint8Array | number[]; 280 | paused: boolean; 281 | } 282 | export type KeyType = { FeeKey: null } | { BridgeGroupKey: null }; 283 | export interface TransferNft { 284 | dip721_contract: Principal; 285 | token_id: bigint; 286 | mint_with: string; 287 | caller: Principal; 288 | token_data: string; 289 | } 290 | export interface TransferNftBatch { 291 | dip721_contract: Principal; 292 | token_datas: Array; 293 | mint_with: string; 294 | caller: Principal; 295 | token_ids: Array; 296 | } 297 | export interface UnfreezeNft { 298 | uri: string; 299 | token_id: bigint; 300 | caller: Principal; 301 | burner: Principal; 302 | } 303 | export interface UnfreezeNftBatch { 304 | uris: Array; 305 | caller: Principal; 306 | token_ids: Array; 307 | burner: Principal; 308 | } 309 | export interface ValidateCleanLogs { 310 | action_id: bigint; 311 | } 312 | export interface ValidateSetGroupKey { 313 | group_key: Uint8Array | number[]; 314 | } 315 | export interface ValidateSetPause { 316 | pause: boolean; 317 | } 318 | export interface ValidateTransferNft { 319 | to: Principal; 320 | mint_with: Principal; 321 | token_url: string; 322 | } 323 | export interface ValidateTransferNftBatch { 324 | to: Principal; 325 | mint_with: Array; 326 | token_urls: Array; 327 | } 328 | export interface ValidateUnfreezeNft { 329 | to: Principal; 330 | dip_contract: Principal; 331 | token_id: bigint; 332 | } 333 | export interface ValidateUnfreezeNftBatch { 334 | to: Principal; 335 | dip_contracts: Array; 336 | token_ids: Array; 337 | } 338 | export interface ValidateWhitelistDip721 { 339 | dip_contract: Principal; 340 | } 341 | export interface ValidateWithdrawFees { 342 | to: Principal; 343 | } 344 | export type ValidatedEvent = { 345 | ValidatedPause?: { paused: boolean }; 346 | ValidatedUpdateKey?: { 347 | key: Uint8Array | number[]; 348 | key_type: KeyType; 349 | }; 350 | ValidatedUnfreeze?: { 351 | to: Principal; 352 | token_id: bigint; 353 | contract: Principal; 354 | }; 355 | ValidatedUnfreezeBatch?: { 356 | to: Principal; 357 | contracts: Array; 358 | token_ids: Array; 359 | }; 360 | ValidatedMintBatch?: { 361 | mint_with: Array; 362 | token_ids: Uint32Array | number[]; 363 | }; 364 | ValidatedMint?: { token_id: number; mint_with: Principal }; 365 | ValidatedFeeWithdraw?: { to: Principal; block_index: bigint }; 366 | }; 367 | 368 | export interface _SERVICE { 369 | add_whitelist: ActorMethod< 370 | [bigint, ValidateWhitelistDip721, Uint8Array | number[]], 371 | undefined 372 | >; 373 | clean_logs: ActorMethod< 374 | [bigint, ValidateCleanLogs, Uint8Array | number[]], 375 | undefined 376 | >; 377 | encode_transfer_tx: ActorMethod< 378 | [number, number, string, bigint, bigint], 379 | Uint8Array | number[] 380 | >; 381 | encode_validate_transfer_nft: ActorMethod< 382 | [bigint, ValidateTransferNft], 383 | Uint8Array | number[] 384 | >; 385 | encode_validate_transfer_nft_batch: ActorMethod< 386 | [bigint, ValidateTransferNftBatch], 387 | Uint8Array | number[] 388 | >; 389 | encode_validate_unfreeze_nft: ActorMethod< 390 | [bigint, ValidateUnfreezeNft], 391 | Uint8Array | number[] 392 | >; 393 | encode_validate_unfreeze_nft_batch: ActorMethod< 394 | [bigint, ValidateUnfreezeNftBatch], 395 | Uint8Array | number[] 396 | >; 397 | encode_withdraw_fees: ActorMethod< 398 | [bigint, ValidateWithdrawFees], 399 | Uint8Array | number[] 400 | >; 401 | freeze_nft: ActorMethod< 402 | [bigint, Principal, bigint, bigint, string, string, Uint8Array | number[]], 403 | bigint 404 | >; 405 | freeze_nft_batch: ActorMethod< 406 | [ 407 | bigint, 408 | Principal, 409 | Array, 410 | bigint, 411 | string, 412 | string, 413 | Uint8Array | number[] 414 | ], 415 | bigint 416 | >; 417 | get_config: ActorMethod<[], Config>; 418 | get_event: ActorMethod<[bigint], [] | [[BridgeEventCtx, BridgeEvent]]>; 419 | get_validated_event: ActorMethod<[bigint], [] | ValidatedEvent[]>; 420 | is_whitelisted: ActorMethod<[Principal], boolean>; 421 | set_fee_group_key: ActorMethod< 422 | [bigint, ValidateSetGroupKey, Uint8Array | number[]], 423 | number 424 | >; 425 | set_group_key: ActorMethod< 426 | [bigint, ValidateSetGroupKey, Uint8Array | number[]], 427 | number 428 | >; 429 | set_pause: ActorMethod< 430 | [bigint, ValidateSetPause, Uint8Array | number[]], 431 | number 432 | >; 433 | validate_transfer_nft: ActorMethod< 434 | [bigint, ValidateTransferNft, Uint8Array | number[]], 435 | number 436 | >; 437 | validate_transfer_nft_batch: ActorMethod< 438 | [bigint, ValidateTransferNftBatch, Uint8Array | number[]], 439 | undefined 440 | >; 441 | validate_unfreeze_nft: ActorMethod< 442 | [bigint, ValidateUnfreezeNft, Uint8Array | number[]], 443 | number 444 | >; 445 | validate_unfreeze_nft_batch: ActorMethod< 446 | [bigint, ValidateUnfreezeNftBatch, Uint8Array | number[]], 447 | number 448 | >; 449 | withdraw_fees: ActorMethod< 450 | [bigint, ValidateWithdrawFees, Uint8Array | number[]], 451 | number 452 | >; 453 | withdraw_nft: ActorMethod< 454 | [bigint, Principal, bigint, bigint, string, Uint8Array | number[]], 455 | bigint 456 | >; 457 | withdraw_nft_batch: ActorMethod< 458 | [bigint, Principal, Array, bigint, string, Uint8Array | number[]], 459 | bigint 460 | >; 461 | } 462 | -------------------------------------------------------------------------------- /src/helpers/dfinity/xpnft.idl.ts: -------------------------------------------------------------------------------- 1 | import type { Principal } from "@dfinity/principal"; 2 | import type { ActorMethod } from "@dfinity/agent"; 3 | 4 | export type AccountIdentifier = string; 5 | export type AccountIdentifier__1 = string; 6 | export interface AllowanceRequest { 7 | token: TokenIdentifier; 8 | owner: User; 9 | spender: Principal; 10 | } 11 | export interface ApproveRequest { 12 | token: TokenIdentifier; 13 | subaccount: [] | [SubAccount]; 14 | allowance: Balance; 15 | spender: Principal; 16 | } 17 | export type Balance = bigint; 18 | export interface BalanceRequest { 19 | token: TokenIdentifier; 20 | user: User; 21 | } 22 | export type BalanceResponse = { ok: Balance } | { err: CommonError__1 }; 23 | export type Balance__1 = bigint; 24 | export type CommonError = { InvalidToken: TokenIdentifier } | { Other: string }; 25 | export type CommonError__1 = 26 | | { InvalidToken: TokenIdentifier } 27 | | { Other: string }; 28 | export type Extension = string; 29 | export type Memo = Uint8Array | number[]; 30 | export type Metadata = 31 | | { 32 | fungible: { 33 | decimals: number; 34 | metadata: [] | [Uint8Array | number[]]; 35 | name: string; 36 | symbol: string; 37 | }; 38 | } 39 | | { nonfungible: { metadata: [] | [Uint8Array | number[]] } }; 40 | export interface MintRequest { 41 | to: User; 42 | metadata: [] | [Uint8Array | number[]]; 43 | } 44 | export type Result = { ok: Balance__1 } | { err: CommonError }; 45 | export type Result_1 = { ok: Metadata } | { err: CommonError }; 46 | export type Result_2 = { ok: AccountIdentifier__1 } | { err: CommonError }; 47 | export type SubAccount = Uint8Array | number[]; 48 | export type TokenIdentifier = string; 49 | export type TokenIdentifier__1 = string; 50 | export type TokenIndex = number; 51 | export interface TransferRequest { 52 | to: User; 53 | token: TokenIdentifier; 54 | notify: boolean; 55 | from: User; 56 | memo: Memo; 57 | subaccount: [] | [SubAccount]; 58 | amount: Balance; 59 | } 60 | export type TransferResponse = 61 | | { ok: Balance } 62 | | { 63 | err: 64 | | { CannotNotify: AccountIdentifier } 65 | | { InsufficientBalance: null } 66 | | { InvalidToken: TokenIdentifier } 67 | | { Rejected: null } 68 | | { Unauthorized: AccountIdentifier } 69 | | { Other: string }; 70 | }; 71 | export type User = { principal: Principal } | { address: AccountIdentifier }; 72 | export interface XPNFT { 73 | acceptCycles: ActorMethod<[], undefined>; 74 | allowance: ActorMethod<[AllowanceRequest], Result>; 75 | approve: ActorMethod<[ApproveRequest], undefined>; 76 | availableCycles: ActorMethod<[], bigint>; 77 | balance: ActorMethod<[BalanceRequest], BalanceResponse>; 78 | bearer: ActorMethod<[TokenIdentifier__1], Result_2>; 79 | burnNFT: ActorMethod<[number], TokenIndex>; 80 | extensions: ActorMethod<[], Array>; 81 | getAllowances: ActorMethod<[], Array<[TokenIndex, Principal]>>; 82 | getMinter: ActorMethod<[], Principal>; 83 | getRegistry: ActorMethod<[], Array<[TokenIndex, AccountIdentifier__1]>>; 84 | getTokens: ActorMethod<[], Array<[TokenIndex, Metadata]>>; 85 | metadata: ActorMethod<[TokenIdentifier__1], Result_1>; 86 | mintNFT: ActorMethod<[MintRequest], TokenIndex>; 87 | setMinter: ActorMethod<[Principal], undefined>; 88 | supply: ActorMethod<[TokenIdentifier__1], Result>; 89 | transfer: ActorMethod<[TransferRequest], TransferResponse>; 90 | } 91 | export interface XPNFTSERVICE extends XPNFT {} 92 | 93 | export const xpnftIdl = ({ IDL }: any) => { 94 | const TokenIdentifier = IDL.Text; 95 | const AccountIdentifier = IDL.Text; 96 | const User = IDL.Variant({ 97 | principal: IDL.Principal, 98 | address: AccountIdentifier, 99 | }); 100 | const AllowanceRequest = IDL.Record({ 101 | token: TokenIdentifier, 102 | owner: User, 103 | spender: IDL.Principal, 104 | }); 105 | const Balance__1 = IDL.Nat; 106 | const CommonError = IDL.Variant({ 107 | InvalidToken: TokenIdentifier, 108 | Other: IDL.Text, 109 | }); 110 | const Result = IDL.Variant({ ok: Balance__1, err: CommonError }); 111 | const SubAccount = IDL.Vec(IDL.Nat8); 112 | const Balance = IDL.Nat; 113 | const ApproveRequest = IDL.Record({ 114 | token: TokenIdentifier, 115 | subaccount: IDL.Opt(SubAccount), 116 | allowance: Balance, 117 | spender: IDL.Principal, 118 | }); 119 | const BalanceRequest = IDL.Record({ 120 | token: TokenIdentifier, 121 | user: User, 122 | }); 123 | const CommonError__1 = IDL.Variant({ 124 | InvalidToken: TokenIdentifier, 125 | Other: IDL.Text, 126 | }); 127 | const BalanceResponse = IDL.Variant({ 128 | ok: Balance, 129 | err: CommonError__1, 130 | }); 131 | const TokenIdentifier__1 = IDL.Text; 132 | const AccountIdentifier__1 = IDL.Text; 133 | const Result_2 = IDL.Variant({ 134 | ok: AccountIdentifier__1, 135 | err: CommonError, 136 | }); 137 | const TokenIndex = IDL.Nat32; 138 | const Extension = IDL.Text; 139 | const Metadata = IDL.Variant({ 140 | fungible: IDL.Record({ 141 | decimals: IDL.Nat8, 142 | metadata: IDL.Opt(IDL.Vec(IDL.Nat8)), 143 | name: IDL.Text, 144 | symbol: IDL.Text, 145 | }), 146 | nonfungible: IDL.Record({ metadata: IDL.Opt(IDL.Vec(IDL.Nat8)) }), 147 | }); 148 | const Result_1 = IDL.Variant({ ok: Metadata, err: CommonError }); 149 | const MintRequest = IDL.Record({ 150 | to: User, 151 | metadata: IDL.Opt(IDL.Vec(IDL.Nat8)), 152 | }); 153 | const Memo = IDL.Vec(IDL.Nat8); 154 | const TransferRequest = IDL.Record({ 155 | to: User, 156 | token: TokenIdentifier, 157 | notify: IDL.Bool, 158 | from: User, 159 | memo: Memo, 160 | subaccount: IDL.Opt(SubAccount), 161 | amount: Balance, 162 | }); 163 | const TransferResponse = IDL.Variant({ 164 | ok: Balance, 165 | err: IDL.Variant({ 166 | CannotNotify: AccountIdentifier, 167 | InsufficientBalance: IDL.Null, 168 | InvalidToken: TokenIdentifier, 169 | Rejected: IDL.Null, 170 | Unauthorized: AccountIdentifier, 171 | Other: IDL.Text, 172 | }), 173 | }); 174 | const XPNFT = IDL.Service({ 175 | acceptCycles: IDL.Func([], [], []), 176 | allowance: IDL.Func([AllowanceRequest], [Result], ["query"]), 177 | approve: IDL.Func([ApproveRequest], [], []), 178 | availableCycles: IDL.Func([], [IDL.Nat], ["query"]), 179 | balance: IDL.Func([BalanceRequest], [BalanceResponse], ["query"]), 180 | bearer: IDL.Func([TokenIdentifier__1], [Result_2], ["query"]), 181 | burnNFT: IDL.Func([IDL.Nat32], [TokenIndex], []), 182 | extensions: IDL.Func([], [IDL.Vec(Extension)], ["query"]), 183 | getAllowances: IDL.Func( 184 | [], 185 | [IDL.Vec(IDL.Tuple(TokenIndex, IDL.Principal))], 186 | ["query"] 187 | ), 188 | getMinter: IDL.Func([], [IDL.Principal], ["query"]), 189 | getRegistry: IDL.Func( 190 | [], 191 | [IDL.Vec(IDL.Tuple(TokenIndex, AccountIdentifier__1))], 192 | ["query"] 193 | ), 194 | getTokens: IDL.Func( 195 | [], 196 | [IDL.Vec(IDL.Tuple(TokenIndex, Metadata))], 197 | ["query"] 198 | ), 199 | metadata: IDL.Func([TokenIdentifier__1], [Result_1], ["query"]), 200 | mintNFT: IDL.Func([MintRequest], [TokenIndex], []), 201 | setMinter: IDL.Func([IDL.Principal], [], []), 202 | supply: IDL.Func([TokenIdentifier__1], [Result], ["query"]), 203 | transfer: IDL.Func([TransferRequest], [TransferResponse], []), 204 | }); 205 | return XPNFT; 206 | }; 207 | -------------------------------------------------------------------------------- /src/helpers/elrond/v3Bridge_abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildInfo": { 3 | "rustc": { 4 | "version": "1.71.0-nightly", 5 | "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", 6 | "commitDate": "2023-05-25", 7 | "channel": "Nightly", 8 | "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" 9 | }, 10 | "contractCrate": { 11 | "name": "bridge", 12 | "version": "0.0.0" 13 | }, 14 | "framework": { 15 | "name": "multiversx-sc", 16 | "version": "0.44.0" 17 | } 18 | }, 19 | "name": "BridgeContract", 20 | "constructor": { 21 | "inputs": [ 22 | { 23 | "name": "public_key", 24 | "type": "Address" 25 | } 26 | ], 27 | "outputs": [] 28 | }, 29 | "endpoints": [ 30 | { 31 | "name": "tokens", 32 | "mutability": "readonly", 33 | "inputs": [], 34 | "outputs": [ 35 | { 36 | "type": "variadic>", 37 | "multi_result": true 38 | } 39 | ] 40 | }, 41 | { 42 | "name": "validators", 43 | "mutability": "readonly", 44 | "inputs": [ 45 | { 46 | "name": "address", 47 | "type": "Address" 48 | } 49 | ], 50 | "outputs": [ 51 | { 52 | "type": "variadic", 53 | "multi_result": true 54 | } 55 | ] 56 | }, 57 | { 58 | "name": "validatorsCount", 59 | "mutability": "readonly", 60 | "inputs": [], 61 | "outputs": [ 62 | { 63 | "type": "u64" 64 | } 65 | ] 66 | }, 67 | { 68 | "name": "uniqueIdentifier", 69 | "mutability": "readonly", 70 | "inputs": [], 71 | "outputs": [ 72 | { 73 | "type": "variadic", 74 | "multi_result": true 75 | } 76 | ] 77 | }, 78 | { 79 | "name": "originalToDuplicateMapping", 80 | "mutability": "readonly", 81 | "inputs": [], 82 | "outputs": [ 83 | { 84 | "type": "variadic,ContractInfo>>", 85 | "multi_result": true 86 | } 87 | ] 88 | }, 89 | { 90 | "name": "duplicateToOriginalMapping", 91 | "mutability": "readonly", 92 | "inputs": [], 93 | "outputs": [ 94 | { 95 | "type": "variadic,ContractInfo>>", 96 | "multi_result": true 97 | } 98 | ] 99 | }, 100 | { 101 | "name": "addValidator", 102 | "mutability": "mutable", 103 | "inputs": [ 104 | { 105 | "name": "new_validator_public_key", 106 | "type": "Address" 107 | }, 108 | { 109 | "name": "signatures", 110 | "type": "List" 111 | } 112 | ], 113 | "outputs": [] 114 | }, 115 | { 116 | "name": "claimValidatorRewards", 117 | "mutability": "mutable", 118 | "inputs": [ 119 | { 120 | "name": "validator", 121 | "type": "Address" 122 | }, 123 | { 124 | "name": "signatures", 125 | "type": "List" 126 | } 127 | ], 128 | "outputs": [] 129 | }, 130 | { 131 | "name": "lock721", 132 | "mutability": "mutable", 133 | "payableInTokens": ["*"], 134 | "inputs": [ 135 | { 136 | "name": "token_id", 137 | "type": "TokenIdentifier" 138 | }, 139 | { 140 | "name": "destination_chain", 141 | "type": "bytes" 142 | }, 143 | { 144 | "name": "destination_user_address", 145 | "type": "bytes" 146 | }, 147 | { 148 | "name": "source_nft_contract_address", 149 | "type": "TokenIdentifier" 150 | }, 151 | { 152 | "name": "nonce", 153 | "type": "u64" 154 | } 155 | ], 156 | "outputs": [] 157 | }, 158 | { 159 | "name": "lock1155", 160 | "mutability": "mutable", 161 | "payableInTokens": ["*"], 162 | "inputs": [ 163 | { 164 | "name": "token_id", 165 | "type": "TokenIdentifier" 166 | }, 167 | { 168 | "name": "destination_chain", 169 | "type": "bytes" 170 | }, 171 | { 172 | "name": "destination_user_address", 173 | "type": "bytes" 174 | }, 175 | { 176 | "name": "source_nft_contract_address", 177 | "type": "TokenIdentifier" 178 | }, 179 | { 180 | "name": "amount", 181 | "type": "BigUint" 182 | }, 183 | { 184 | "name": "nonce", 185 | "type": "u64" 186 | } 187 | ], 188 | "outputs": [] 189 | }, 190 | { 191 | "name": "claimNft721", 192 | "mutability": "mutable", 193 | "payableInTokens": ["EGLD"], 194 | "inputs": [ 195 | { 196 | "name": "data", 197 | "type": "ClaimData" 198 | }, 199 | { 200 | "name": "signatures", 201 | "type": "List" 202 | }, 203 | { 204 | "name": "uris", 205 | "type": "multi", 206 | "multi_arg": true 207 | } 208 | ], 209 | "outputs": [] 210 | }, 211 | { 212 | "name": "claimNft1155", 213 | "mutability": "mutable", 214 | "payableInTokens": ["EGLD"], 215 | "inputs": [ 216 | { 217 | "name": "data", 218 | "type": "ClaimData" 219 | }, 220 | { 221 | "name": "signatures", 222 | "type": "List" 223 | }, 224 | { 225 | "name": "uris", 226 | "type": "multi", 227 | "multi_arg": true 228 | } 229 | ], 230 | "outputs": [] 231 | }, 232 | { 233 | "name": "collections", 234 | "mutability": "readonly", 235 | "inputs": [ 236 | { 237 | "name": "identifier", 238 | "type": "bytes" 239 | } 240 | ], 241 | "outputs": [ 242 | { 243 | "type": "TokenIdentifier" 244 | } 245 | ] 246 | } 247 | ], 248 | "events": [ 249 | { 250 | "identifier": "AddNewValidator", 251 | "inputs": [ 252 | { 253 | "name": "validator", 254 | "type": "Address", 255 | "indexed": true 256 | } 257 | ] 258 | }, 259 | { 260 | "identifier": "Locked", 261 | "inputs": [ 262 | { 263 | "name": "token_id", 264 | "type": "u64", 265 | "indexed": true 266 | }, 267 | { 268 | "name": "destination_chain", 269 | "type": "bytes", 270 | "indexed": true 271 | }, 272 | { 273 | "name": "destination_user_address", 274 | "type": "bytes", 275 | "indexed": true 276 | }, 277 | { 278 | "name": "source_nft_contract_address", 279 | "type": "TokenIdentifier", 280 | "indexed": true 281 | }, 282 | { 283 | "name": "token_amount", 284 | "type": "BigUint", 285 | "indexed": true 286 | }, 287 | { 288 | "name": "nft_type", 289 | "type": "bytes", 290 | "indexed": true 291 | }, 292 | { 293 | "name": "chain", 294 | "type": "bytes", 295 | "indexed": true 296 | } 297 | ] 298 | }, 299 | { 300 | "identifier": "UnLock721", 301 | "inputs": [ 302 | { 303 | "name": "to", 304 | "type": "Address", 305 | "indexed": true 306 | }, 307 | { 308 | "name": "token_id", 309 | "type": "u64", 310 | "indexed": true 311 | }, 312 | { 313 | "name": "contract_address", 314 | "type": "TokenIdentifier", 315 | "indexed": true 316 | } 317 | ] 318 | }, 319 | { 320 | "identifier": "UnLock1155", 321 | "inputs": [ 322 | { 323 | "name": "to", 324 | "type": "Address", 325 | "indexed": true 326 | }, 327 | { 328 | "name": "token_id", 329 | "type": "u64", 330 | "indexed": true 331 | }, 332 | { 333 | "name": "contract_address", 334 | "type": "TokenIdentifier", 335 | "indexed": true 336 | }, 337 | { 338 | "name": "amount", 339 | "type": "BigUint", 340 | "indexed": true 341 | } 342 | ] 343 | }, 344 | { 345 | "identifier": "Claimed", 346 | "inputs": [ 347 | { 348 | "name": "source_chain", 349 | "type": "bytes", 350 | "indexed": true 351 | }, 352 | { 353 | "name": "transaction_hash", 354 | "type": "bytes", 355 | "indexed": true 356 | } 357 | ] 358 | }, 359 | { 360 | "identifier": "RewardValidator", 361 | "inputs": [ 362 | { 363 | "name": "validator", 364 | "type": "Address", 365 | "indexed": true 366 | } 367 | ] 368 | } 369 | ], 370 | "esdtAttributes": [], 371 | "hasCallback": true, 372 | "types": { 373 | "ClaimData": { 374 | "type": "struct", 375 | "fields": [ 376 | { 377 | "name": "token_id", 378 | "type": "bytes" 379 | }, 380 | { 381 | "name": "source_chain", 382 | "type": "bytes" 383 | }, 384 | { 385 | "name": "destination_chain", 386 | "type": "bytes" 387 | }, 388 | { 389 | "name": "destination_user_address", 390 | "type": "Address" 391 | }, 392 | { 393 | "name": "source_nft_contract_address", 394 | "type": "bytes" 395 | }, 396 | { 397 | "name": "name", 398 | "type": "bytes" 399 | }, 400 | { 401 | "name": "symbol", 402 | "type": "bytes" 403 | }, 404 | { 405 | "name": "royalty", 406 | "type": "BigUint" 407 | }, 408 | { 409 | "name": "royalty_receiver", 410 | "type": "Address" 411 | }, 412 | { 413 | "name": "attrs", 414 | "type": "bytes" 415 | }, 416 | { 417 | "name": "transaction_hash", 418 | "type": "bytes" 419 | }, 420 | { 421 | "name": "token_amount", 422 | "type": "BigUint" 423 | }, 424 | { 425 | "name": "nft_type", 426 | "type": "bytes" 427 | }, 428 | { 429 | "name": "fee", 430 | "type": "BigUint" 431 | } 432 | ] 433 | }, 434 | "ContractInfo": { 435 | "type": "struct", 436 | "fields": [ 437 | { 438 | "name": "chain", 439 | "type": "bytes" 440 | }, 441 | { 442 | "name": "address", 443 | "type": "bytes" 444 | } 445 | ] 446 | }, 447 | "SignatureInfo": { 448 | "type": "struct", 449 | "fields": [ 450 | { 451 | "name": "public_key", 452 | "type": "Address" 453 | }, 454 | { 455 | "name": "sig", 456 | "type": "bytes" 457 | } 458 | ] 459 | }, 460 | "TokenInfo": { 461 | "type": "struct", 462 | "fields": [ 463 | { 464 | "name": "token_id", 465 | "type": "u64" 466 | }, 467 | { 468 | "name": "chain", 469 | "type": "bytes" 470 | }, 471 | { 472 | "name": "contract_address", 473 | "type": "bytes" 474 | } 475 | ] 476 | }, 477 | "Validator": { 478 | "type": "struct", 479 | "fields": [ 480 | { 481 | "name": "added", 482 | "type": "bool" 483 | }, 484 | { 485 | "name": "pending_reward", 486 | "type": "BigUint" 487 | } 488 | ] 489 | } 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/helpers/evm/web3_erc20.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Web3 Implementation for cross chain traits 3 | * @module 4 | */ 5 | import BigNumber from "bignumber.js"; 6 | import { 7 | BigNumber as EthBN, 8 | ContractTransaction, 9 | ethers, 10 | Signer, 11 | VoidSigner, 12 | Wallet, 13 | } from "ethers"; 14 | import { Provider, TransactionResponse } from "@ethersproject/providers"; 15 | import { 16 | Erc1155Minter__factory, 17 | MinterERC20__factory, 18 | UserNftMinter__factory, 19 | PaymentToken__factory, 20 | } from "xpnet-web3-contracts"; 21 | import { 22 | BaseWeb3Helper, 23 | EthNftInfo, 24 | MintArgs, 25 | NFT_METHOD_MAP, 26 | NftInfo, 27 | TransactionStatus, 28 | Web3Helper, 29 | Web3Params, 30 | } from "../.."; 31 | import { txnUnderpricedPolyWorkaround as UnderpricedWorkaround } from "./web3_utils"; 32 | 33 | /** 34 | * Create an object implementing minimal utilities for a web3 chain 35 | * 36 | * @param provider An ethers.js provider object 37 | */ 38 | export async function baseWeb3HelperFactory( 39 | provider: Provider 40 | ): Promise { 41 | const w3 = provider; 42 | 43 | return { 44 | async balance(address: string): Promise { 45 | const bal = await w3.getBalance(address); 46 | 47 | // ethers BigNumber is not compatible with our bignumber 48 | return new BigNumber(bal.toString()); 49 | }, 50 | async deployErc721(owner: Signer): Promise { 51 | const factory = new UserNftMinter__factory(owner); 52 | const contract = await factory.deploy(); 53 | 54 | return contract.address; 55 | }, 56 | async mintNftErc1155(owner: Signer, { contract }) { 57 | const erc1155 = Erc1155Minter__factory.connect(contract!, owner); 58 | const tx = await erc1155.mintNft(await owner.getAddress()); 59 | 60 | return tx; 61 | }, 62 | async mintNft( 63 | owner: Signer, 64 | { contract, uri }: MintArgs 65 | ): Promise { 66 | const erc721 = UserNftMinter__factory.connect(contract!, owner); 67 | 68 | const txm = await erc721.mint(uri, { gasLimit: 1000000 }); 69 | return txm; 70 | }, 71 | }; 72 | } 73 | 74 | export type Web3ERC20Params = Web3Params & { 75 | paymentTokenAddress: string; 76 | }; 77 | 78 | export async function web3ERC20HelperFactory( 79 | params: Web3ERC20Params 80 | ): Promise { 81 | const txnUnderpricedPolyWorkaround = 82 | params.nonce == 7 ? UnderpricedWorkaround : () => Promise.resolve(); 83 | const w3 = params.provider; 84 | const { minter_addr, provider } = params; 85 | const minter = MinterERC20__factory.connect(minter_addr, provider); 86 | 87 | async function notifyValidator( 88 | fromHash: string, 89 | actionId?: string, 90 | type?: string, 91 | toChain?: number, 92 | txFees?: string, 93 | senderAddress?: string, 94 | targetAddress?: string, 95 | nftUri?: string, 96 | tokenId?: string, 97 | contract?: string 98 | ): Promise { 99 | await params.notifier.notifyWeb3( 100 | params.nonce, 101 | fromHash, 102 | actionId, 103 | type, 104 | toChain, 105 | txFees, 106 | senderAddress, 107 | targetAddress, 108 | nftUri, 109 | tokenId, 110 | contract 111 | ); 112 | } 113 | 114 | async function getTransaction(hash: string) { 115 | let trx; 116 | let fails = 0; 117 | while (!trx && fails < 7) { 118 | trx = await provider.getTransaction(hash); 119 | await new Promise((resolve) => 120 | setTimeout(() => resolve("wait"), 5000 + fails * 2) 121 | ); 122 | fails++; 123 | } 124 | 125 | return trx as TransactionResponse; 126 | } 127 | 128 | async function extractAction(txr: TransactionResponse): Promise { 129 | const receipt = await txr.wait(); 130 | const log = receipt.logs.find((log) => log.address === minter.address); 131 | if (log === undefined) { 132 | throw Error("Couldn't extract action_id"); 133 | } 134 | 135 | const evdat = minter.interface.parseLog(log); 136 | const action_id: string = evdat.args[0].toString(); 137 | return action_id; 138 | } 139 | 140 | const isApprovedForMinter = async ( 141 | id: NftInfo, 142 | signer: Signer, 143 | _: string 144 | ) => { 145 | const erc = NFT_METHOD_MAP[id.native.contractType].umt.connect( 146 | id.native.contract, 147 | signer 148 | ); 149 | return await NFT_METHOD_MAP[id.native.contractType].approved( 150 | erc as any, 151 | await signer.getAddress(), 152 | minter_addr, 153 | id.native.tokenId, 154 | params.nonce === 0x1d ? {} : undefined 155 | ); 156 | }; 157 | 158 | const approveForMinter = async ( 159 | id: NftInfo, 160 | sender: Signer, 161 | txFees: BigNumber, 162 | overrides: ethers.Overrides | undefined 163 | ) => { 164 | const isApproved = await isApprovedForMinter(id, sender, minter_addr); 165 | if (isApproved) { 166 | return undefined; 167 | } 168 | const erc = NFT_METHOD_MAP[id.native.contractType].umt.connect( 169 | id.native.contract, 170 | sender 171 | ); 172 | 173 | const receipt = await NFT_METHOD_MAP[id.native.contractType].approve( 174 | erc as any, 175 | minter_addr, 176 | id.native.tokenId, 177 | txnUnderpricedPolyWorkaround, 178 | params.nonce === 0x1d ? {} : undefined, 179 | overrides 180 | ); 181 | await receipt.wait(); 182 | 183 | const erc20 = PaymentToken__factory.connect( 184 | params.paymentTokenAddress, 185 | sender 186 | ); 187 | const approval = await erc20.approve( 188 | minter_addr, 189 | EthBN.from(txFees.toString()) 190 | ); 191 | 192 | return approval.hash; 193 | }; 194 | 195 | const base = await baseWeb3HelperFactory(params.provider); 196 | 197 | return { 198 | ...base, 199 | XpNft: params.erc721_addr, 200 | XpNft1155: params.erc1155_addr, 201 | getParams: () => params, 202 | approveForMinter, 203 | getProvider: () => provider, 204 | async estimateValidateUnfreezeNft(_to, _id, _mW) { 205 | const gas = await provider.getGasPrice(); 206 | return new BigNumber(gas.mul(150_000).toString()); 207 | }, 208 | getFeeMargin() { 209 | return params.feeMargin; 210 | }, 211 | isApprovedForMinter, 212 | preTransfer: (s, id, _fee, args) => 213 | approveForMinter(id, s, _fee, args?.overrides), 214 | extractAction, 215 | async isContractAddress(address) { 216 | const code = await provider.getCode(address); 217 | return code !== "0x"; 218 | }, 219 | estimateContractDeploy: async () => new BigNumber(0), 220 | estimateUserStoreDeploy: async () => new BigNumber(0), 221 | getNonce: () => params.nonce, 222 | async preTransferRawTxn(id, address, _value) { 223 | const isApproved = await isApprovedForMinter( 224 | id, 225 | new VoidSigner(address, provider), 226 | minter_addr 227 | ); 228 | 229 | if (isApproved) { 230 | return undefined; 231 | } 232 | 233 | const erc = UserNftMinter__factory.connect( 234 | id.native.contract, 235 | new VoidSigner(address, provider) 236 | ); 237 | 238 | const approvetxn = await erc.populateTransaction.approve( 239 | minter_addr, 240 | id.native.tokenId 241 | ); 242 | 243 | return approvetxn; 244 | }, 245 | 246 | async extractTxnStatus(txn) { 247 | const status = (await (await provider.getTransaction(txn)).wait()).status; 248 | if (status === undefined) { 249 | return TransactionStatus.PENDING; 250 | } 251 | if (status === 1) { 252 | return TransactionStatus.SUCCESS; 253 | } else if (status === 0) { 254 | return TransactionStatus.FAILURE; 255 | } 256 | return TransactionStatus.UNKNOWN; 257 | }, 258 | async getTokenURI(contract, tokenId) { 259 | if (ethers.utils.isAddress(contract) && tokenId) { 260 | const erc721 = UserNftMinter__factory.connect(contract!, provider); 261 | return await erc721.tokenURI(tokenId).catch(() => ""); 262 | } 263 | return ""; 264 | }, 265 | async unfreezeWrappedNftBatch(signer, chainNonce, to, nfts, txFees) { 266 | const tx = await minter 267 | .connect(signer) 268 | .populateTransaction.withdrawNftBatch( 269 | to, 270 | chainNonce, 271 | nfts.map((nft) => nft.native.tokenId), 272 | new Array(nfts.length).fill(1), 273 | nfts[0].native.contract, 274 | EthBN.from(txFees.toString()) 275 | ); 276 | await txnUnderpricedPolyWorkaround(tx); 277 | const res = await signer.sendTransaction(tx); 278 | 279 | // await notifyValidator( 280 | // res.hash, 281 | // await extractAction(res), 282 | // "Unfreeze", 283 | // chainNonce.toString(), 284 | // txFees.toString(), 285 | // await signer.getAddress(), 286 | // to, 287 | // res.data 288 | // ); 289 | await notifyValidator(res.hash); 290 | 291 | return res; 292 | }, 293 | async transferNftBatchToForeign( 294 | signer, 295 | chainNonce, 296 | to, 297 | nfts, 298 | mintWith, 299 | txFees 300 | ) { 301 | const tx = await minter 302 | .connect(signer) 303 | .populateTransaction.freezeErc1155Batch( 304 | nfts[0].native.contract, 305 | nfts.map((nft) => nft.native.tokenId), 306 | new Array(nfts.length).fill(1), 307 | chainNonce, 308 | to, 309 | mintWith, 310 | EthBN.from(txFees.toString()) 311 | ); 312 | await txnUnderpricedPolyWorkaround(tx); 313 | 314 | const res = await signer.sendTransaction(tx); 315 | 316 | await notifyValidator(res.hash); 317 | 318 | return res; 319 | }, 320 | async estimateValidateTransferNftBatch(_to, nfts, _mintWith) { 321 | const gasPrice = await w3.getGasPrice(); 322 | const gas = 40_000 + 60_000 * nfts.length; 323 | return new BigNumber(gasPrice.mul(gas).toString()); 324 | }, 325 | async estimateValidateUnfreezeNftBatch(_to, nfts) { 326 | const gasPrice = await w3.getGasPrice(); 327 | const gas = 40_000 + 60_000 * nfts.length; 328 | return new BigNumber(gasPrice.mul(gas).toString()); 329 | }, 330 | createWallet(privateKey: string): Wallet { 331 | return new Wallet(privateKey, provider); 332 | }, 333 | async transferNftToForeign( 334 | sender: Signer, 335 | chain_nonce: number, 336 | to: string, 337 | id: NftInfo, 338 | txFees: BigNumber, 339 | mintWith: string, 340 | gasLimit: ethers.BigNumberish | undefined = undefined, 341 | gasPrice 342 | ): Promise { 343 | await approveForMinter(id, sender, txFees, { gasPrice }); 344 | const method = NFT_METHOD_MAP[id.native.contractType].freeze; 345 | 346 | const tx = await minter 347 | .connect(sender) 348 | .populateTransaction[method]( 349 | id.native.contract, 350 | id.native.tokenId, 351 | chain_nonce, 352 | to, 353 | mintWith, 354 | EthBN.from(txFees.toString()), 355 | { 356 | gasLimit, 357 | } 358 | ); 359 | await txnUnderpricedPolyWorkaround(tx); 360 | 361 | const txr: TransactionResponse | unknown = await sender 362 | .sendTransaction(tx) 363 | .catch((e) => { 364 | if (params.nonce === 33) { 365 | return e; 366 | } else throw e; 367 | }); 368 | let txHash: string; 369 | if (params.nonce === 0x1d) { 370 | //@ts-ignore checked hedera 371 | txHash = txr["transactionId"]; 372 | } 373 | if (params.nonce === 33) { 374 | //@ts-ignore checked abeychain 375 | txHash = txr["returnedHash"] || txr.hash; 376 | } else { 377 | //@ts-ignore checked normal evm 378 | txHash = txr.hash; 379 | } 380 | 381 | await notifyValidator( 382 | //@ts-ignore 383 | txHash, 384 | await extractAction(await getTransaction(txHash)), 385 | "Transfer", 386 | chain_nonce, 387 | txFees.toString(), 388 | await sender.getAddress(), 389 | to, 390 | id.uri, 391 | id.native.tokenId, 392 | id.native.contract 393 | ); 394 | return params.nonce === 33 395 | ? await provider.getTransaction(txHash) 396 | : (txr as TransactionResponse); 397 | }, 398 | async unfreezeWrappedNft( 399 | sender: Signer, 400 | to: string, 401 | id: NftInfo, 402 | txFees: BigNumber, 403 | nonce 404 | ): Promise { 405 | const txn = await minter 406 | .connect(sender) 407 | .populateTransaction.withdrawNft( 408 | to, 409 | nonce, 410 | id.native.tokenId, 411 | id.native.contract, 412 | EthBN.from(txFees.toString()) 413 | ); 414 | 415 | await txnUnderpricedPolyWorkaround(txn); 416 | const res = await sender.sendTransaction(txn); 417 | 418 | await notifyValidator( 419 | res.hash, 420 | await extractAction(res), 421 | "Unfreeze", 422 | Number(nonce), 423 | txFees.toString(), 424 | await sender.getAddress(), 425 | to, 426 | id.uri, 427 | id.native.tokenId, 428 | id.native.contract 429 | ); 430 | 431 | return res; 432 | }, 433 | async estimateValidateTransferNft( 434 | _to: string, 435 | _nftUri: NftInfo, 436 | _mintWith 437 | ): Promise { 438 | const gas = await provider.getGasPrice(); 439 | 440 | return new BigNumber(gas.mul(150_000).toString()); 441 | }, 442 | validateAddress(adr) { 443 | return Promise.resolve(ethers.utils.isAddress(adr)); 444 | }, 445 | isNftWhitelisted(nft) { 446 | return minter.nftWhitelist(nft.native.contract); 447 | }, 448 | async lockNFT() { 449 | return undefined; 450 | }, 451 | async claimV3NFT() { 452 | return undefined; 453 | }, 454 | async getClaimData() { 455 | return undefined as any; 456 | }, 457 | async getTokenInfo() { 458 | return undefined as any; 459 | }, 460 | async getNftOrigin() { 461 | return undefined as any; 462 | }, 463 | }; 464 | } 465 | -------------------------------------------------------------------------------- /src/helpers/evm/web3_utils.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { ethers, PopulatedTransaction } from "ethers"; 3 | 4 | export const txnUnderpricedPolyWorkaround = async ( 5 | utx: PopulatedTransaction 6 | ) => { 7 | const res = await axios 8 | .get( 9 | "https://gpoly.blockscan.com/gasapi.ashx?apikey=key&method=pendingpooltxgweidata" 10 | ) 11 | .catch(async () => { 12 | return await axios.get("https://gasstation-mainnet.matic.network/v2"); 13 | }); 14 | const { result, fast } = res.data; 15 | const trackerGas = result?.rapidgaspricegwei || fast?.maxFee; 16 | 17 | if (trackerGas) { 18 | const sixtyGwei = ethers.utils.parseUnits( 19 | Math.ceil(trackerGas).toString(), 20 | "gwei" 21 | ); 22 | utx.maxFeePerGas = sixtyGwei; 23 | utx.maxPriorityFeePerGas = sixtyGwei; 24 | } 25 | }; 26 | 27 | export const getWrapped = async (uri: string) => { 28 | return (await axios.get(uri).catch(() => ({ data: undefined }))).data 29 | ?.wrapped; 30 | }; 31 | 32 | export const tryTimes = 33 | (times: number, condition: string = "") => 34 | async (cb: (...args: any) => Promise, ...args: any) => { 35 | for (let i = 0; i < times; i++) { 36 | try { 37 | const gasLimit = args.at(-1).gasLimit; 38 | return await cb(...args.slice(0, -1), { 39 | gasLimit: gasLimit * (i + 1), 40 | }); 41 | } catch (error: any) { 42 | console.log(`Attempt ${i + 1} failed: retry`); 43 | if (condition && !error.message.includes(condition)) { 44 | throw error; 45 | } 46 | } 47 | } 48 | throw new Error("PRC is not responding, please try later"); 49 | }; 50 | -------------------------------------------------------------------------------- /src/helpers/near.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { BN } from "bn.js"; 3 | 4 | import { 5 | Account, 6 | connect, 7 | Near, 8 | keyStores, 9 | WalletConnection, 10 | KeyPair, 11 | InMemorySigner, 12 | } from "near-api-js"; 13 | 14 | import { 15 | FinalExecutionOutcome, 16 | getTransactionLastResult, 17 | } from "near-api-js/lib/providers"; 18 | 19 | import { Chain } from "../consts"; 20 | import { SignatureService } from "../services/estimator"; 21 | import { EvNotifier } from "../services/notifier"; 22 | import { WhitelistedService } from "../services/whitelisted"; 23 | import { 24 | ChainNonceGet, 25 | EstimateTxFees, 26 | FeeMargins, 27 | GetFeeMargins, 28 | GetProvider, 29 | MintNft, 30 | NftInfo, 31 | TransferNftForeign, 32 | UnfreezeForeignNft, 33 | ValidateAddress, 34 | BalanceCheck, 35 | PreTransfer, 36 | WhitelistCheck, 37 | EstimateDeployFees, 38 | } from "./chain"; 39 | 40 | type NearTxResult = [FinalExecutionOutcome, any]; 41 | 42 | type NearPreTransferArgs = { 43 | to: string; 44 | receiver: string; 45 | }; 46 | 47 | export type NearParams = { 48 | readonly networkId: string; 49 | readonly nonce: number; 50 | readonly rpcUrl: string; 51 | readonly bridge: string; 52 | readonly xpnft: string; 53 | readonly feeMargin: FeeMargins; 54 | readonly notifier: EvNotifier; 55 | readonly walletUrl: string; 56 | readonly helperUrl: string; 57 | readonly whitelisted: WhitelistedService; 58 | readonly signatureSvc: SignatureService; 59 | }; 60 | export type NearNFT = { 61 | tokenId: string; 62 | contract: string; 63 | }; 64 | 65 | export type Metadata = { 66 | title?: string; 67 | description?: string; 68 | media?: string; 69 | mediaHash: Uint8Array | null; 70 | issued_at: string | null; 71 | expires_at: string | null; 72 | starts_at: string | null; 73 | updated_at: string | null; 74 | extra?: string; 75 | reference: string | null; 76 | referenceHash: Uint8Array | null; 77 | }; 78 | 79 | export interface NearMintArgs { 80 | contract: string; 81 | token_id: string; 82 | token_owner_id: string; 83 | metadata: Metadata; 84 | } 85 | 86 | interface BrowserMethods { 87 | connectWallet(): Promise; 88 | getUserMinter(keypair: string, address: string): Promise; 89 | } 90 | 91 | interface NotifyMethod { 92 | notify(hash: string): Promise; 93 | } 94 | 95 | export type NearHelper = ChainNonceGet & 96 | BalanceCheck & 97 | TransferNftForeign & 98 | UnfreezeForeignNft & 99 | MintNft & 100 | EstimateTxFees & 101 | EstimateDeployFees & 102 | Pick< 103 | PreTransfer, 104 | "preTransfer" 105 | > & 106 | ValidateAddress & { 107 | XpNft: string; 108 | nftList(owner: Account, contract: string): Promise[]>; 109 | } & GetFeeMargins & 110 | GetProvider & 111 | BrowserMethods & 112 | NotifyMethod & 113 | WhitelistCheck; 114 | 115 | export async function nearHelperFactory({ 116 | networkId, 117 | bridge, 118 | rpcUrl, 119 | xpnft, 120 | feeMargin, 121 | notifier, 122 | whitelisted, 123 | walletUrl, 124 | signatureSvc, 125 | helperUrl, 126 | }: NearParams): Promise { 127 | const near = await connect({ 128 | nodeUrl: rpcUrl, 129 | networkId, 130 | headers: {}, 131 | }); 132 | 133 | const isApproved = async ( 134 | account: Account, 135 | nft: NftInfo 136 | ): Promise => { 137 | const { tokenId: token_id, contract } = nft.native; 138 | 139 | const tokenData = await account.viewFunction({ 140 | args: { 141 | token_id, 142 | }, 143 | contractId: contract, 144 | methodName: "nft_token", 145 | }); 146 | 147 | const approval_id = tokenData.approved_account_ids[bridge]; 148 | if (!approval_id) return false; 149 | 150 | return await account.viewFunction({ 151 | args: { 152 | token_id, 153 | approved_account_id: bridge, 154 | approval_id, 155 | }, 156 | contractId: contract, 157 | methodName: "nft_is_approved", 158 | }); 159 | }; 160 | 161 | const getWalletCallbackUrl = (params: string) => { 162 | let walletCallbackUrl: string | undefined = undefined; 163 | if (globalThis.window !== undefined) { 164 | const network = 165 | location.pathname.match(/^\/(staging|testnet)\/.+/)?.at(1) || ""; 166 | const query = new URLSearchParams( 167 | window.location.search.replace("?", "") 168 | ); 169 | const wid = query.get("wid"); 170 | const selectedNearWallet = query.get("selectedNearWallet"); 171 | walletCallbackUrl = `${location.protocol}//${ 172 | location.host 173 | }/${network}/connect?${wid ? `wid=${wid}&` : ""}${ 174 | selectedNearWallet ? `selectedNearWallet=${selectedNearWallet}&` : "" 175 | }${params}`; 176 | } 177 | return walletCallbackUrl; 178 | }; 179 | 180 | const notifyValidators = async (hash: string) => { 181 | //await new Promise((r) => setTimeout(r, 15_000)); 182 | return notifier.notifyNear(hash); 183 | }; 184 | 185 | return { 186 | notify: notifyValidators, 187 | async estimateValidateTransferNft(_to, _metadata, _mintWith) { 188 | return new BigNumber(0); // TODO 189 | }, 190 | async estimateValidateUnfreezeNft(_to, _metadata, _mintWith) { 191 | return new BigNumber(0); // TODO 192 | }, 193 | estimateContractDeploy: async () => { 194 | return new BigNumber("5000000000000000000000000"); 195 | }, 196 | //async estimateContractDe 197 | getNonce() { 198 | return Chain.NEAR; 199 | }, 200 | async balance(address: string) { 201 | const res = ( 202 | await new Account(near.connection, address).getAccountBalance() 203 | ).available; 204 | return new BigNumber(res); 205 | }, 206 | async mintNft(owner, options) { 207 | const result = await owner.functionCall({ 208 | contractId: options.contract, 209 | methodName: "nft_mint", 210 | args: { 211 | token_id: options.token_id, 212 | token_owner_id: options.token_owner_id, 213 | token_metadata: options.metadata, 214 | }, 215 | attachedDeposit: new BN("10000000000000000000000"), // 0.01 Near 216 | }); 217 | return [result, getTransactionLastResult(result)]; 218 | }, 219 | async nftList(owner, contract) { 220 | const result = await owner.functionCall({ 221 | contractId: contract, 222 | methodName: "nft_tokens_for_owner", 223 | args: { account_id: owner.accountId }, 224 | }); 225 | const res = getTransactionLastResult(result) as any[]; 226 | 227 | return res.map((r) => { 228 | return { 229 | native: { 230 | tokenId: r.token_id, 231 | contract, 232 | }, 233 | collectionIdent: contract, 234 | uri: r.metadata.extra || r.metadata.media, 235 | }; 236 | }); 237 | }, 238 | async preTransfer(sender, nft, _fee, args) { 239 | if (await isApproved(sender, nft)) { 240 | return undefined; 241 | } 242 | if (!args) { 243 | throw new Error("Missing args"); 244 | } 245 | const { receiver, to } = args; 246 | const walletCallbackUrl = getWalletCallbackUrl( 247 | `NEARTRX=true&type=approve&to=${to}&receiver=${encodeURIComponent( 248 | receiver 249 | )}&tokenId=${encodeURIComponent(nft.native.tokenId)}` 250 | ); 251 | 252 | const result = await sender.functionCall({ 253 | contractId: nft.native.contract, 254 | methodName: "nft_approve", 255 | args: { 256 | token_id: nft.native.tokenId, 257 | account_id: bridge, 258 | }, 259 | attachedDeposit: new BN("1000000000000000000000"), // 0.001 Near 260 | ...(walletCallbackUrl ? { walletCallbackUrl } : {}), 261 | }); 262 | return result.transaction_outcome.id; 263 | }, 264 | XpNft: xpnft, 265 | async transferNftToForeign(sender, chain_nonce, to, id, txFees, mint_with) { 266 | const walletCallbackUrl = getWalletCallbackUrl( 267 | `NEARTRX=true&type=transfer&to=${chain_nonce}&receiver=${encodeURIComponent( 268 | to 269 | )}&tokenId=${encodeURIComponent( 270 | id.native.tokenId 271 | )}&contract=${encodeURIComponent(id.native.contract)}` 272 | ); 273 | const res = await signatureSvc 274 | .getSignatureNear( 275 | Chain.NEAR, 276 | chain_nonce as any, 277 | id.native.tokenId, 278 | id.collectionIdent, 279 | id.native.tokenId, 280 | to 281 | ) 282 | .catch(() => undefined); 283 | 284 | if (res) { 285 | const tokenData = await sender.viewFunction({ 286 | args: { 287 | token_id: id.native.tokenId, 288 | }, 289 | contractId: id.native.contract, 290 | methodName: "nft_token", 291 | }); 292 | 293 | let approval_id = tokenData.approved_account_ids[bridge]; 294 | if (!approval_id) { 295 | approval_id = null; 296 | } 297 | 298 | const result = await sender.functionCall({ 299 | contractId: bridge, 300 | args: { 301 | token_id: id.native.tokenId, 302 | chain_nonce, 303 | to, 304 | amt: new BigNumber(txFees) /*.div(2)*/, 305 | mint_with, 306 | token_contract: id.native.contract, 307 | ...(res?.signature 308 | ? { 309 | sig_data: [...Buffer.from(res.signature, "hex")], 310 | } 311 | : {}), 312 | approval_id, 313 | }, 314 | methodName: "freeze_nft", 315 | attachedDeposit: new BN(res?.fee) /*.div(new BN(2))*/, 316 | gas: new BN("300000000000000"), 317 | ...(walletCallbackUrl ? { walletCallbackUrl } : {}), 318 | }); 319 | 320 | await notifyValidators(result.transaction.hash); 321 | return [result, getTransactionLastResult(result)]; 322 | } else { 323 | return undefined; 324 | } 325 | }, 326 | getFeeMargin() { 327 | return feeMargin; 328 | }, 329 | getProvider() { 330 | return near; 331 | }, 332 | async unfreezeWrappedNft(sender, to, id, txFees, nonce) { 333 | const walletCallbackUrl = getWalletCallbackUrl( 334 | `NEARTRX=true&type=unfreeze&to=${nonce}&receiver=${encodeURIComponent( 335 | to 336 | )}&tokenId=${encodeURIComponent( 337 | id.native.tokenId 338 | )}&contract=${encodeURIComponent(id.native.contract)}` 339 | ); 340 | 341 | const res = await signatureSvc.getSignatureNear( 342 | Chain.NEAR, 343 | nonce as any, 344 | id.native.tokenId, 345 | id.collectionIdent, 346 | id.native.tokenId, 347 | to 348 | ); 349 | 350 | const result = await sender.functionCall({ 351 | contractId: bridge, 352 | args: { 353 | token_id: id.native.tokenId, 354 | chain_nonce: nonce, 355 | to, 356 | amt: parseInt(txFees.toString()), 357 | token_contract: id.native.contract, 358 | sig_data: [...Buffer.from(res?.signature, "hex")], 359 | }, 360 | methodName: "withdraw_nft", 361 | attachedDeposit: new BN(res?.fee), 362 | gas: new BN("300000000000000"), 363 | ...(walletCallbackUrl ? { walletCallbackUrl } : {}), 364 | }); 365 | 366 | await notifyValidators(result.transaction.hash); 367 | return [result, getTransactionLastResult(result)]; 368 | }, 369 | async validateAddress(adr) { 370 | try { 371 | await new Account(near.connection, adr).getAccountBalance(); 372 | return true; 373 | } catch (e) { 374 | return false; 375 | } 376 | }, 377 | 378 | async connectWallet(url?: string) { 379 | if (typeof window === "undefined") { 380 | throw new Error("Browser method only"); 381 | } 382 | const nearConnection = await connect({ 383 | networkId, 384 | nodeUrl: rpcUrl, 385 | keyStore: new keyStores.BrowserLocalStorageKeyStore(), 386 | headers: {}, 387 | walletUrl: url || walletUrl, 388 | helperUrl, 389 | }); 390 | const wc = new WalletConnection(nearConnection, ""); 391 | 392 | return wc; 393 | }, 394 | 395 | async isNftWhitelisted(nft: NftInfo) { 396 | /*const result: boolean = await signer 397 | .viewFunction({ 398 | args: { 399 | contract_id: nft.native.contract, 400 | }, 401 | contractId: bridge, 402 | methodName: "is_whitelist", 403 | }) 404 | .catch(() => false);*/ 405 | 406 | const res = ( 407 | await whitelisted.get(`/near/whitelisted/${nft.native.contract}`) 408 | )?.data; 409 | return Boolean(res?.isWhitelisted); 410 | }, 411 | 412 | async getUserMinter(keypair: string, address: string) { 413 | const keyStore = new keyStores.InMemoryKeyStore(); 414 | const keyPair = KeyPair.fromString(keypair); 415 | keyStore.setKey(networkId, address, keyPair); 416 | 417 | const signer = new InMemorySigner(keyStore); 418 | 419 | const provider = await connect({ 420 | headers: {}, 421 | nodeUrl: rpcUrl, 422 | networkId, 423 | signer, 424 | }); 425 | 426 | return provider; 427 | }, 428 | }; 429 | } 430 | -------------------------------------------------------------------------------- /src/helpers/secret.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { 3 | Bech32, 4 | SecretNetworkClient, 5 | addressToBytes, 6 | toBase64, 7 | } from "secretjs"; 8 | import { 9 | Extension, 10 | Snip721MintOptions, 11 | } from "secretjs/dist/extensions/snip721/types"; 12 | import { Snip721GetTokensResponse } from "secretjs/dist/extensions/snip721/msg/GetTokens"; 13 | import { EvNotifier } from "../services/notifier"; 14 | import { 15 | BalanceCheck, 16 | ChainNonceGet, 17 | EstimateTxFees, 18 | FeeMargins, 19 | GetFeeMargins, 20 | GetProvider, 21 | MintNft, 22 | NftInfo, 23 | PreTransfer, 24 | TransferNftForeign, 25 | UnfreezeForeignNft, 26 | ValidateAddress, 27 | WhitelistCheck, 28 | } from "./chain"; 29 | 30 | export type SecretNftInfo = { 31 | contract: string; 32 | contractHash: string; 33 | chainId: string; 34 | tokenId: string; 35 | vk: string; 36 | metadata: Extension | undefined; 37 | }; 38 | export type SecretMintArgs = { 39 | url: string; 40 | contract?: SecretContract; 41 | }; 42 | 43 | type SecretSigner = SecretNetworkClient; 44 | 45 | type GetOwnedTokensResponse = Snip721GetTokensResponse & { 46 | generic_err?: { msg: string }; 47 | }; 48 | 49 | export type SecretHelper = TransferNftForeign< 50 | SecretSigner, 51 | SecretNftInfo, 52 | any 53 | > & 54 | UnfreezeForeignNft & 55 | ValidateAddress & 56 | EstimateTxFees & 57 | ChainNonceGet & 58 | WhitelistCheck & 59 | PreTransfer & 60 | BalanceCheck & 61 | GetFeeMargins & { XpNft: string } & GetProvider & 62 | MintNft & { 63 | nftList( 64 | owner: string, 65 | 66 | viewingKey: string, 67 | contract: string, 68 | codeHash?: string 69 | ): Promise[]>; 70 | setViewingKey( 71 | client: SecretNetworkClient, 72 | contract: string, 73 | vk: string 74 | ): Promise; 75 | isApprovedForMinter( 76 | sender: SecretSigner, 77 | nft: NftInfo 78 | ): Promise; 79 | }; 80 | 81 | export type SecretContract = { 82 | contractAddress: string; 83 | codeHash: string; 84 | }; 85 | 86 | export type SecretParams = { 87 | rpcUrl: string; 88 | chainId: string; 89 | notifier: EvNotifier; 90 | bridge: SecretContract; 91 | xpnft: SecretContract; 92 | umt: SecretContract; 93 | feeMargin: FeeMargins; 94 | }; 95 | 96 | // TODO 97 | const TRANSFER_GASL = new BigNumber(0); 98 | 99 | // TODO 100 | const UNFREEZE_GASL = new BigNumber(0); 101 | 102 | export async function secretHelperFactory( 103 | p: SecretParams 104 | ): Promise { 105 | const queryClient = await SecretNetworkClient.create({ 106 | grpcWebUrl: p.rpcUrl, 107 | chainId: p.chainId, 108 | }); 109 | 110 | // TODO 111 | const gasPrice = 1; 112 | 113 | async function isApprovedForMinter( 114 | sender: SecretSigner, 115 | nft: NftInfo 116 | ) { 117 | const approval = await sender.query.snip721.GetTokenInfo({ 118 | auth: { 119 | viewer: { 120 | address: sender.address, 121 | viewing_key: nft.native.vk, 122 | }, 123 | }, 124 | contract: { 125 | address: nft.collectionIdent, 126 | codeHash: nft.native.contractHash, 127 | }, 128 | token_id: nft.native.tokenId, 129 | }); 130 | for (let appr of approval.all_nft_info.access.approvals) { 131 | if ( 132 | (appr as any)["spender"].toLowerCase() === 133 | p.bridge.contractAddress.toLowerCase() 134 | ) { 135 | return true; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | async function preTransfer( 142 | sender: SecretSigner, 143 | nft: NftInfo 144 | ) { 145 | // TODO: check if approved 146 | if (await isApprovedForMinter(sender, nft)) { 147 | return undefined; 148 | } 149 | const res = await sender.tx.compute.executeContract( 150 | { 151 | sender: sender.address, 152 | contractAddress: nft.native.contract, 153 | codeHash: nft.native.contractHash, 154 | msg: { 155 | approve: { 156 | spender: p.bridge.contractAddress, 157 | token_id: nft.native.tokenId, 158 | }, 159 | }, 160 | }, 161 | { 162 | waitForCommit: true, 163 | gasLimit: 250_000, 164 | } 165 | ); 166 | return res.transactionHash; 167 | } 168 | 169 | return { 170 | getFeeMargin() { 171 | return p.feeMargin; 172 | }, 173 | getProvider() { 174 | return queryClient; 175 | }, 176 | getNonce: () => 0x18, 177 | balance: async (address) => { 178 | const b = await queryClient.query.bank.balance({ 179 | address, 180 | denom: "uscrt", 181 | }); 182 | 183 | return new BigNumber(b.balance?.amount || 0); 184 | }, 185 | isApprovedForMinter, 186 | async mintNft(signer, args) { 187 | const minter = args.contract ? args.contract : p.umt; 188 | const tx = await signer.tx.compute.executeContract( 189 | { 190 | contractAddress: minter.contractAddress, 191 | codeHash: minter.codeHash, 192 | msg: { 193 | mint_nft: { 194 | public_metadata: { 195 | token_uri: args.url, 196 | }, 197 | owner: signer.address, 198 | transferable: true, 199 | }, 200 | } as Snip721MintOptions, 201 | sender: signer.address, 202 | }, 203 | { 204 | waitForCommit: true, 205 | gasLimit: 500_000, 206 | } 207 | ); 208 | return tx; 209 | }, 210 | XpNft: `${p.xpnft.contractAddress},${p.xpnft.codeHash}`, 211 | validateAddress: async (a) => { 212 | try { 213 | Bech32.decode(a); 214 | return true; 215 | } catch { 216 | return false; 217 | } 218 | }, 219 | async nftList(owner, vk, contractAddress, codeHash) { 220 | const auth = { 221 | viewer: { 222 | viewing_key: vk, 223 | address: owner, 224 | }, 225 | }; 226 | if (!codeHash) { 227 | codeHash = await queryClient.query.compute.contractCodeHash( 228 | contractAddress 229 | ); 230 | } 231 | const contract = { 232 | address: contractAddress, 233 | codeHash: codeHash || "", 234 | }; 235 | 236 | const { token_list, generic_err } = 237 | (await queryClient.query.snip721.GetOwnedTokens({ 238 | contract, 239 | auth, 240 | owner, 241 | })) as GetOwnedTokensResponse; 242 | 243 | if (generic_err) throw new Error(generic_err.msg); 244 | 245 | const response: NftInfo[] = []; 246 | 247 | await Promise.all( 248 | token_list.tokens.map(async (token) => { 249 | const tokenInfo = await queryClient.query.snip721.GetTokenInfo({ 250 | contract, 251 | auth, 252 | token_id: token, 253 | }); 254 | 255 | response.push({ 256 | collectionIdent: contractAddress, 257 | uri: tokenInfo.all_nft_info.info?.token_uri || "", 258 | native: { 259 | chainId: p.chainId, 260 | contract: contractAddress, 261 | contractHash: codeHash || "", 262 | tokenId: token, 263 | vk, 264 | metadata: tokenInfo.all_nft_info.info?.extension, 265 | }, 266 | }); 267 | }) 268 | ); 269 | return response; 270 | }, 271 | estimateValidateTransferNft: async () => { 272 | return TRANSFER_GASL.times(gasPrice); 273 | }, 274 | estimateValidateUnfreezeNft: async () => { 275 | return UNFREEZE_GASL.times(gasPrice); 276 | }, 277 | async setViewingKey(client, contract, vk) { 278 | const tx = await client.tx.snip721.setViewingKey( 279 | { 280 | contractAddress: contract, 281 | msg: { 282 | set_viewing_key: { 283 | key: vk, 284 | }, 285 | }, 286 | sender: client.address, 287 | }, 288 | { 289 | waitForCommit: true, 290 | gasLimit: 500_000, 291 | } 292 | ); 293 | return tx; 294 | }, 295 | preTransfer, 296 | preUnfreeze: preTransfer, 297 | transferNftToForeign: async (wallet, chainNonce, to, nft, fee, mw) => { 298 | const tx = await wallet.tx.compute.executeContract( 299 | { 300 | sender: wallet.address, 301 | contractAddress: p.bridge.contractAddress, 302 | codeHash: p.bridge.codeHash, 303 | msg: { 304 | freeze_nft: { 305 | contract: nft.native.contract, 306 | contract_hash: nft.native.contractHash, 307 | viewer: { 308 | viewing_key: nft.native.vk, 309 | address: wallet.address, 310 | }, 311 | token_id: nft.native.tokenId, 312 | to, 313 | chain_nonce: chainNonce, 314 | minter: mw, 315 | }, 316 | }, 317 | sentFunds: [ 318 | { 319 | denom: "uscrt", 320 | amount: fee.toString(10), 321 | }, 322 | ], 323 | }, 324 | { waitForCommit: true, gasLimit: 500_000 } 325 | ); 326 | 327 | await p.notifier.notifySecret(tx.transactionHash, nft.native.vk); 328 | 329 | return tx; 330 | }, 331 | unfreezeWrappedNft: async (wallet, to, nft, fee, chainNonce) => { 332 | const tx = await wallet.tx.compute.executeContract( 333 | { 334 | sender: wallet.address, 335 | contractAddress: p.bridge.contractAddress, 336 | codeHash: p.bridge.codeHash, 337 | msg: { 338 | withdraw_nft: { 339 | burner: nft.native.contract, 340 | burner_hash: nft.native.contractHash, 341 | token_id: nft.native.tokenId, 342 | to, 343 | chain_nonce: Number(chainNonce), 344 | }, 345 | }, 346 | sentFunds: [ 347 | { 348 | denom: "uscrt", 349 | amount: fee.toString(10), 350 | }, 351 | ], 352 | }, 353 | { waitForCommit: true, gasLimit: 500_000 } 354 | ); 355 | 356 | await p.notifier.notifySecret(tx.transactionHash, nft.native.vk); 357 | 358 | return tx; 359 | }, 360 | isNftWhitelisted: async (nft) => { 361 | if (!nft.native?.contract) return false; 362 | const result = await queryClient.query.compute.queryContract({ 363 | contractAddress: p.bridge.contractAddress, 364 | codeHash: p.bridge.codeHash, 365 | query: { 366 | get_whitelisted: { 367 | addr: toBase64(addressToBytes(nft.native.contract)), 368 | }, 369 | }, 370 | }); 371 | 372 | return typeof result === "boolean" ? result : false; 373 | }, 374 | }; 375 | } 376 | -------------------------------------------------------------------------------- /src/helpers/solana/index.ts: -------------------------------------------------------------------------------- 1 | /*import { 2 | Metaplex, 3 | bundlrStorage, 4 | walletAdapterIdentity, 5 | } from "@metaplex-foundation/js";*/ 6 | import { 7 | Wallet, 8 | BN, 9 | Program, 10 | AnchorProvider, 11 | Spl, 12 | } from "@project-serum/anchor"; 13 | import { 14 | Account, 15 | createAssociatedTokenAccountInstruction, 16 | getAccount, 17 | getAssociatedTokenAddress, 18 | TokenAccountNotFoundError, 19 | TokenInvalidAccountOwnerError, 20 | TokenInvalidMintError, 21 | TokenInvalidOwnerError, 22 | TOKEN_PROGRAM_ID, 23 | } from "@solana/spl-token"; 24 | import { 25 | Connection, 26 | PublicKey, 27 | Transaction, 28 | //LAMPORTS_PER_SOL, 29 | } from "@solana/web3.js"; 30 | 31 | import BigNumber from "bignumber.js"; 32 | import { Chain } from "../.."; 33 | import { EvNotifier } from "../../services/notifier"; 34 | import { 35 | ChainNonceGet, 36 | EstimateTxFees, 37 | FeeMargins, 38 | GetFeeMargins, 39 | GetProvider, 40 | TransferNftForeign, 41 | UnfreezeForeignNft, 42 | ValidateAddress, 43 | BalanceCheck, 44 | MintNft, 45 | EstimateDeployFees, 46 | } from "../chain"; 47 | import { IDL } from "./idl"; 48 | 49 | export type SolanaSigner = Wallet; 50 | 51 | export type SolanaNft = { 52 | nftMint: string; 53 | }; 54 | 55 | type SolanaMintArgs = { 56 | uri: string; 57 | }; 58 | 59 | export type SolanaHelper = ChainNonceGet & 60 | BalanceCheck & 61 | MintNft & 62 | TransferNftForeign & 63 | UnfreezeForeignNft & 64 | EstimateTxFees & 65 | ValidateAddress & { 66 | connection: Connection; 67 | } & { XpNft: string } & GetFeeMargins & 68 | GetProvider & 69 | EstimateDeployFees; 70 | 71 | export type SolanaParams = { 72 | endpoint: string; 73 | bridgeContractAddr: string; 74 | xpnftAddr: string; 75 | notifier: EvNotifier; 76 | feeMargin: FeeMargins; 77 | }; 78 | 79 | // Based on https://github.com/solana-labs/solana-program-library/blob/118bd047aa0f1ba1930b5bc4639d40aa2a375ccb/token/js/src/actions/getOrCreateAssociatedTokenAccount.ts 80 | async function getOrCreateAssociatedTokenAccount( 81 | connection: Connection, 82 | payer: SolanaSigner, 83 | mint: PublicKey, 84 | owner: PublicKey, 85 | allowOwnerOffCurve = false 86 | ) { 87 | const provider = new AnchorProvider(connection, payer, {}); 88 | 89 | const associatedToken = await getAssociatedTokenAddress( 90 | mint, 91 | owner, 92 | allowOwnerOffCurve 93 | ); 94 | 95 | // This is the optimal logic, considering TX fee, client-side computation, RPC roundtrips and guaranteed idempotent. 96 | // Sadly we can't do this atomically. 97 | let account: Account; 98 | try { 99 | account = await getAccount(connection, associatedToken); 100 | } catch (error: unknown) { 101 | // TokenAccountNotFoundError can be possible if the associated address has already received some lamports, 102 | // becoming a system account. Assuming program derived addressing is safe, this is the only case for the 103 | // TokenInvalidAccountOwnerError in this code path. 104 | if ( 105 | error instanceof TokenAccountNotFoundError || 106 | error instanceof TokenInvalidAccountOwnerError 107 | ) { 108 | // As this isn't atomic, it's possible others can create associated accounts meanwhile. 109 | try { 110 | const transaction = new Transaction().add( 111 | createAssociatedTokenAccountInstruction( 112 | payer.publicKey, 113 | associatedToken, 114 | owner, 115 | mint 116 | ) 117 | ); 118 | 119 | await provider.sendAndConfirm(transaction); 120 | } catch (error: unknown) { 121 | // Ignore all errors; for now there is no API-compatible way to selectively ignore the expected 122 | // instruction error if the associated account exists already. 123 | } 124 | 125 | // Now this should always succeed 126 | account = await getAccount(connection, associatedToken); 127 | } else { 128 | throw error; 129 | } 130 | } 131 | 132 | if (!account.mint.equals(mint)) throw new TokenInvalidMintError(); 133 | if (!account.owner.equals(owner)) throw new TokenInvalidOwnerError(); 134 | 135 | return account; 136 | } 137 | 138 | export async function solanaHelper(args: SolanaParams): Promise { 139 | const conn = new Connection(args.endpoint); 140 | 141 | async function getOrCreateTokenAccount( 142 | mint: PublicKey, 143 | owner: PublicKey, 144 | provider: AnchorProvider 145 | ) { 146 | const tokenProgram = Spl.token(provider); 147 | const program = Spl.associatedToken(provider); 148 | 149 | const [associatedToken] = await PublicKey.findProgramAddress( 150 | [owner.toBuffer(), tokenProgram.programId.toBuffer(), mint.toBuffer()], 151 | program.programId 152 | ); 153 | 154 | try { 155 | const tokenAccount = await tokenProgram.account.token.fetch( 156 | associatedToken 157 | ); 158 | return { 159 | address: associatedToken, 160 | owner: tokenAccount.authority, 161 | ...tokenAccount, 162 | }; 163 | } catch (e) { 164 | try { 165 | await program.methods 166 | .create() 167 | .accounts({ 168 | mint, 169 | owner, 170 | associatedAccount: associatedToken, 171 | }) 172 | .rpc(); 173 | 174 | const tokenAccount = await tokenProgram.account.token.fetch( 175 | associatedToken 176 | ); 177 | return { 178 | address: associatedToken, 179 | owner: tokenAccount.authority, 180 | ...tokenAccount, 181 | }; 182 | } catch (e) { 183 | throw e; 184 | } 185 | } 186 | } 187 | 188 | return { 189 | XpNft: args.xpnftAddr, 190 | connection: conn, 191 | async balance(address: string) { 192 | return new BigNumber(await conn.getBalance(new PublicKey(address))); 193 | }, 194 | getNonce: () => Chain.SOLANA, 195 | async transferNftToForeign(sender, chain_nonce, to, id, txFees, mintWith) { 196 | const provider = new AnchorProvider(conn, sender, {}); 197 | const bridgeContract = new Program( 198 | IDL, 199 | args.bridgeContractAddr, 200 | provider 201 | ); 202 | 203 | const [bridge, bridgeBump] = await PublicKey.findProgramAddress( 204 | [Buffer.from("bridge")], 205 | bridgeContract.programId 206 | ); 207 | 208 | const mintAddr = new PublicKey(id.native.nftMint); 209 | const fromTokenAcc = await getOrCreateTokenAccount( 210 | mintAddr, 211 | sender.publicKey, 212 | provider 213 | ); 214 | const toAccount = await getOrCreateTokenAccount( 215 | mintAddr, 216 | bridge, 217 | provider 218 | ); 219 | const tx = await bridgeContract.methods 220 | .freezeNft( 221 | chain_nonce, 222 | to, 223 | new BN(txFees.toString(10)), 224 | mintWith, 225 | bridgeBump 226 | ) 227 | .accounts({ 228 | bridge, 229 | authority: sender.publicKey, 230 | from: fromTokenAcc.address, 231 | to: toAccount.address, 232 | tokenProgram: TOKEN_PROGRAM_ID, 233 | }) 234 | .rpc(); 235 | 236 | await args.notifier.notifySolana(tx); 237 | 238 | return tx; 239 | }, 240 | getFeeMargin() { 241 | return args.feeMargin; 242 | }, 243 | async unfreezeWrappedNft(sender, to, id, txFees, nonce) { 244 | console.log(`Unfreezing`); 245 | const provider = new AnchorProvider(conn, sender, {}); 246 | const bridgeContract = new Program( 247 | IDL, 248 | args.bridgeContractAddr, 249 | provider 250 | ); 251 | 252 | const [bridge, bridgeBump] = await PublicKey.findProgramAddress( 253 | [Buffer.from("bridge")], 254 | bridgeContract.programId 255 | ); 256 | 257 | const mintAddr = new PublicKey(id.native.nftMint); 258 | 259 | const tokenAcc = await getOrCreateAssociatedTokenAccount( 260 | conn, 261 | sender, 262 | mintAddr, 263 | sender.publicKey 264 | ).catch((e) => { 265 | console.error(e); 266 | throw e; 267 | }); 268 | 269 | const tx = await bridgeContract.methods 270 | .withdrawNft(nonce, to, new BN(txFees.toString(10)), bridgeBump) 271 | .accounts({ 272 | bridge, 273 | authority: sender.publicKey, 274 | mint: tokenAcc.mint, 275 | tokenAccount: tokenAcc.address, 276 | tokenProgram: TOKEN_PROGRAM_ID, 277 | }) 278 | .rpc(); 279 | 280 | await args.notifier.notifySolana(tx); 281 | 282 | return tx; 283 | }, 284 | getProvider() { 285 | return conn; 286 | }, 287 | async mintNft() { 288 | /*console.log(Metaplex, walletAdapterIdentity, bundlrStorage); 289 | console.log(args, "args"); 290 | console.log(sender, "sender"); 291 | const provider = new AnchorProvider(conn, sender, {}); 292 | console.log(provider.wallet, "provider"); 293 | 294 | /*const txn = await conn.requestAirdrop( 295 | sender.publicKey, 296 | LAMPORTS_PER_SOL * 2 297 | ); 298 | const block = await conn.getLatestBlockhash(); 299 | const sig = conn.confirmTransaction( 300 | { 301 | blockhash: block.blockhash, 302 | lastValidBlockHeight: block.lastValidBlockHeight, 303 | signature: txn, 304 | }, 305 | "finalized" 306 | ); 307 | console.log(`Airdrop: ${txn}`); 308 | console.log(`sig ${sig}`); 309 | console.log(`Waiting for 5s`); 310 | await new Promise((r) => setTimeout(r, 5000)); 311 | //sender.payer.secretKey. 312 | const _metaplex = Metaplex.make(conn) 313 | .use(walletAdapterIdentity(sender)) 314 | .use(bundlrStorage()); 315 | const nftc = _metaplex.nfts(); 316 | 317 | const _col = await nftc.create( 318 | { 319 | name: "Uniair1", 320 | symbol: "UNIAIRT", 321 | 322 | uri: args.uri, 323 | sellerFeeBasisPoints: 0, 324 | }, 325 | { 326 | commitment: "processed", 327 | } 328 | ); 329 | 330 | console.log(_col);*/ 331 | return ""; 332 | }, 333 | async estimateValidateTransferNft() { 334 | return new BigNumber(0); // TODO 335 | }, 336 | async estimateValidateUnfreezeNft() { 337 | return new BigNumber(0); // TODO 338 | }, 339 | async validateAddress(adr) { 340 | try { 341 | new PublicKey(adr); 342 | return true; 343 | } catch { 344 | return false; 345 | } 346 | }, 347 | async estimateContractDeploy() { 348 | //0.01 349 | return new BigNumber(8000000); 350 | }, 351 | }; 352 | } 353 | -------------------------------------------------------------------------------- /src/helpers/tezos.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BigMapAbstraction, 3 | ContractAbstraction, 4 | ContractMethod, 5 | ContractProvider, 6 | MichelsonMap, 7 | SendParams, 8 | Signer, 9 | TezosToolkit, 10 | TransactionOperation, 11 | TransactionWalletOperation, 12 | Wallet, 13 | WalletProvider, 14 | } from "@taquito/taquito"; 15 | import { 16 | BalanceCheck, 17 | Chain, 18 | ChainNonceGet, 19 | EstimateTxFees, 20 | MintNft, 21 | NftInfo, 22 | PreTransfer, 23 | TransferNftForeign, 24 | UnfreezeForeignNft, 25 | ValidateAddress, 26 | } from ".."; 27 | 28 | import * as utils from "@taquito/utils"; 29 | import BigNumber from "bignumber.js"; 30 | import { EvNotifier } from "../services/notifier"; 31 | import { 32 | EstimateTxFeesBatch, 33 | FeeMargins, 34 | GetFeeMargins, 35 | GetTokenURI, 36 | TransferNftForeignBatch, 37 | UnfreezeForeignNftBatch, 38 | WhitelistCheck, 39 | } from "./chain"; 40 | 41 | type TezosSigner = WalletProvider | Signer; 42 | 43 | export type TezosNftInfo = { 44 | contract: string; 45 | token_id: string; 46 | amt: number; 47 | tokenId: string; 48 | }; 49 | 50 | type TezosMintArgs = { 51 | identifier: string; 52 | contract: string; 53 | uri: string; 54 | to: string; 55 | amt: number; 56 | }; 57 | 58 | export type TezosHelper = TransferNftForeign< 59 | TezosSigner, 60 | TezosNftInfo, 61 | string 62 | > & 63 | MintNft & 64 | BalanceCheck & 65 | UnfreezeForeignNft & 66 | TransferNftForeignBatch & 67 | UnfreezeForeignNftBatch & 68 | EstimateTxFeesBatch & 69 | ValidateAddress & 70 | EstimateTxFees & 71 | ChainNonceGet & 72 | Pick, "preTransfer"> & { 73 | isApprovedForMinter( 74 | nft: NftInfo, 75 | signer: TezosSigner 76 | ): Promise; 77 | } & { 78 | approveForMinter( 79 | address: NftInfo, 80 | sender: TezosSigner 81 | ): Promise; 82 | } & { 83 | XpNft: string; 84 | XpNft1155: string; 85 | } & GetFeeMargins & 86 | WhitelistCheck & 87 | GetTokenURI; 88 | 89 | export type TezosParams = { 90 | Tezos: TezosToolkit; 91 | notifier: EvNotifier; 92 | xpnftAddress: string; 93 | bridgeAddress: string; 94 | validators: string[]; 95 | feeMargin: FeeMargins; 96 | }; 97 | 98 | export async function tezosHelperFactory({ 99 | Tezos, 100 | notifier, 101 | xpnftAddress, 102 | bridgeAddress, 103 | validators, 104 | feeMargin, 105 | }: TezosParams): Promise { 106 | const estimateGas = (validators: string[], baseprice: number) => { 107 | return new BigNumber(baseprice * (validators.length + 1)); 108 | }; 109 | 110 | async function withContract( 111 | sender: TezosSigner, 112 | contract: string, 113 | cb: ( 114 | contract: ContractAbstraction 115 | ) => ContractMethod, 116 | params?: Partial 117 | ) { 118 | if ("publicKeyHash" in sender) { 119 | Tezos.setSignerProvider(sender); 120 | 121 | const contractI = await Tezos.contract.at(contract); 122 | 123 | const res = cb(contractI); 124 | const tx = await res.send(params); 125 | await tx.confirmation(); 126 | return (tx as TransactionOperation).hash; 127 | } else { 128 | Tezos.setWalletProvider(sender); 129 | const contractI = await Tezos.wallet.at(contract); 130 | 131 | const res = cb(contractI); 132 | 133 | const estim = await Tezos.estimate 134 | .transfer(res.toTransferParams(params)) 135 | .catch(() => ({ storageLimit: 0 })); 136 | 137 | if (params) { 138 | if (!params.storageLimit) params.storageLimit = estim.storageLimit; 139 | } else { 140 | params = { storageLimit: estim.storageLimit }; 141 | } 142 | const tx = await res.send(params); 143 | await tx.confirmation(); 144 | return (tx as TransactionWalletOperation).opHash; 145 | } 146 | } 147 | 148 | function withBridge( 149 | sender: TezosSigner, 150 | cb: ( 151 | bridge: ContractAbstraction 152 | ) => ContractMethod, 153 | params?: Partial 154 | ) { 155 | return withContract(sender, bridgeAddress, cb, params); 156 | } 157 | 158 | function getAddress(sender: TezosSigner) { 159 | if ("publicKeyHash" in sender) { 160 | return sender.publicKeyHash(); 161 | } else { 162 | return sender.getPKH(); 163 | } 164 | } 165 | 166 | async function isApprovedForMinter( 167 | nft: NftInfo, 168 | sender: TezosSigner 169 | ) { 170 | const owner = await getAddress(sender); 171 | const contract = await Tezos.contract.at(nft.native.contract); 172 | const storage = await contract.storage<{ 173 | operators?: BigMapAbstraction; 174 | operator?: BigMapAbstraction; 175 | }>(); 176 | 177 | const storageOperator = storage.operator || storage.operators; 178 | const args = storage.operator 179 | ? [bridgeAddress, nft.native.tokenId, owner] 180 | : { 181 | owner, 182 | operator: bridgeAddress, 183 | token_id: nft.native.tokenId, 184 | }; 185 | 186 | const op = await storageOperator?.get(args); 187 | 188 | return op != undefined; 189 | } 190 | 191 | async function notifyValidator(hash: string): Promise { 192 | await notifier.notifyTezos(hash); 193 | } 194 | 195 | async function preTransfer(signer: TezosSigner, nft: NftInfo) { 196 | if (await isApprovedForMinter(nft, signer)) { 197 | return; 198 | } 199 | const owner = await getAddress(signer); 200 | return await withContract(signer, nft.native.contract, (contract) => 201 | contract.methods.update_operators([ 202 | { 203 | add_operator: { 204 | owner, 205 | operator: bridgeAddress, 206 | token_id: nft.native.tokenId, 207 | }, 208 | }, 209 | ]) 210 | ); 211 | } 212 | 213 | let transferNft = async ( 214 | sender: TezosSigner, 215 | chain: number, 216 | to: string, 217 | nft: NftInfo, 218 | fee: BigNumber, 219 | mw: string, 220 | amt: number 221 | ) => { 222 | console.log(`bruh`); 223 | // await preTransfer(sender, nft); 224 | const hash = await withBridge( 225 | sender, 226 | (bridge) => 227 | bridge.methodsObject.freeze_fa2({ 228 | fa2_address: nft.collectionIdent, 229 | token_id: nft.native.tokenId, 230 | chain_nonce: chain, 231 | to, 232 | mint_with: mw, 233 | amt, 234 | }) as any, 235 | { amount: fee.toNumber() / 1e6 } 236 | ); 237 | 238 | notifyValidator(hash); 239 | return hash; 240 | }; 241 | 242 | let unfreezeWrappedNft = async ( 243 | sender: TezosSigner, 244 | to: string, 245 | nft: NftInfo, 246 | fee: BigNumber, 247 | nonce: number, 248 | amt: number 249 | ) => { 250 | const hash = await withBridge( 251 | sender, 252 | (bridge) => { 253 | return bridge.methodsObject.withdraw_nft({ 254 | amt, 255 | burner: nft.native.contract, 256 | chain_nonce: nonce, 257 | to, 258 | token_id: nft.native.tokenId, 259 | }) as any; 260 | }, 261 | { amount: fee.toNumber() / 1e6 } 262 | ); 263 | 264 | notifyValidator(hash); 265 | return hash; 266 | }; 267 | 268 | return { 269 | XpNft: xpnftAddress, 270 | XpNft1155: xpnftAddress, 271 | transferNftToForeign: (sender, chain, to, nft, fee, mw) => 272 | transferNft(sender, chain, to, nft, fee, mw, 1), 273 | 274 | transferNftBatchToForeign: ( 275 | sender, 276 | chain_nonce, 277 | to, 278 | id, 279 | mintWith, 280 | txFees 281 | ) => 282 | transferNft(sender, chain_nonce, to, id[0], txFees, mintWith, id.length), 283 | async balance(address) { 284 | return new BigNumber((await Tezos.tz.getBalance(address)).toString(10)); 285 | }, 286 | unfreezeWrappedNftBatch: (sender, chainNonce, to, nfts, txFees) => 287 | unfreezeWrappedNft(sender, to, nfts[0], txFees, chainNonce, nfts.length), 288 | unfreezeWrappedNft: (sender, to, nft, txFees, chainNonce) => 289 | unfreezeWrappedNft(sender, to, nft, txFees, chainNonce, 1), 290 | async mintNft(signer, { identifier, contract, uri, to, amt }) { 291 | const metadata = new MichelsonMap(); 292 | metadata.set("", utils.char2Bytes(uri)); 293 | return await withContract(signer, contract, (umt) => 294 | umt.methods.mint(to, amt, metadata, identifier) 295 | ); 296 | }, 297 | async validateAddress(adr) { 298 | return Promise.resolve( 299 | utils.validateAddress(adr) === utils.ValidationResult.VALID 300 | ); 301 | }, 302 | getNonce() { 303 | return Chain.TEZOS; 304 | }, 305 | getFeeMargin() { 306 | return feeMargin; 307 | }, 308 | async estimateValidateTransferNft() { 309 | return estimateGas(validators, 1.2e5); 310 | }, 311 | async estimateValidateUnfreezeNft() { 312 | return estimateGas(validators, 1.2e4); 313 | }, 314 | async estimateValidateTransferNftBatch(_, ids) { 315 | return estimateGas(validators, 1.2e5 * ids.length); 316 | }, 317 | async estimateValidateUnfreezeNftBatch(_, ids) { 318 | return estimateGas(validators, 1.2e4 * ids.length); 319 | }, 320 | preTransfer, 321 | isApprovedForMinter, 322 | approveForMinter: (nft, sender) => preTransfer(sender, nft), 323 | async isNftWhitelisted(nft) { 324 | const bridge = await Tezos.contract.at(bridgeAddress); 325 | const storage = await bridge.storage<{ 326 | nft_whitelist: BigMapAbstraction; 327 | }>(); 328 | const whitelisted = await storage.nft_whitelist.get(nft.native.contract); 329 | 330 | return whitelisted == 2; 331 | }, 332 | async getTokenURI(contract, tokenId) { 333 | if (utils.validateAddress(contract) && tokenId) { 334 | const _contract = await Tezos.contract.at(contract); 335 | 336 | const storage = (await _contract.storage()) as any; 337 | const tokenStorage = await storage.token_metadata.get(tokenId); 338 | if (tokenStorage) { 339 | return utils.bytes2Char(tokenStorage.token_info?.get("")); 340 | } 341 | } 342 | return ""; 343 | }, 344 | }; 345 | } 346 | -------------------------------------------------------------------------------- /src/helpers/ton/nwl.ts: -------------------------------------------------------------------------------- 1 | const nwls = [ "EQBPa7td5VDwGltzeSzsp32MkzqzlqHjAOsKtaX0mkMJhq_B", 2 | "EQCFttZHA0tsLteL-w8ymLH3Wa7YeB74CDyVBUB1UtTTKAwG", 3 | "EQBWRp5L4eIT3V6EvZZZxmMgylTQd169b40rnEKDrIzpfUdw", 4 | "EQCwsW0Vkg-ayvgsI7Km04WBg7c-14e0GpxUIMkxg6cD-hFd", 5 | "EQAGV80rmBEN13-JwfdltDwJ0JaAY-EpUQiWVDoI3QCjJybG"]; 6 | -------------------------------------------------------------------------------- /src/helpers/ton/ton-bridge.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import TonWeb, { ContractMethods, ContractOptions } from "tonweb"; 3 | import { Cell as CellF, Slice, parseDictRefs, Address } from "ton"; 4 | import { HttpProvider } from "tonweb/dist/types/providers/http-provider"; 5 | import BigNumber from "bignumber.js"; 6 | 7 | const Contract = TonWeb.Contract; 8 | const Cell = TonWeb.boc.Cell; 9 | 10 | declare type SeqnoMethod = () => SeqnoMethodResult; 11 | 12 | interface SeqnoMethodResult { 13 | call: () => Promise; 14 | } 15 | 16 | interface BridgeOptions extends ContractOptions { 17 | burner: string; 18 | } 19 | interface BridgeMethods extends ContractMethods { 20 | seqno: SeqnoMethod; 21 | getPublicKey: () => Promise; 22 | isInitialized: () => Promise; 23 | getActionId: () => Promise; 24 | getWhitelist: () => Promise; 25 | } 26 | 27 | interface WithdrawParams { 28 | chainNonce: number; 29 | to: Uint8Array; 30 | txFees: BN; 31 | } 32 | 33 | interface FreezeParams { 34 | chainNonce: number; 35 | to: Uint8Array; 36 | mintWith: Uint8Array; 37 | amount?: number | BN; 38 | } 39 | 40 | export class BridgeContract extends Contract { 41 | whiteListedCollections: string[] = []; 42 | nwls: string[] = []; 43 | 44 | constructor(provider: HttpProvider, options: BridgeOptions) { 45 | super(provider, options); 46 | 47 | this.methods.getPublicKey = this.getPublicKey; 48 | this.methods.isInitialized = this.isInitialized; 49 | this.methods.getActionId = this.getActionId; 50 | this.methods.getWhitelist = this.getWhitelist; 51 | } 52 | 53 | serializeUri(uri: string): Uint8Array { 54 | return new TextEncoder().encode(encodeURI(uri)); 55 | } 56 | 57 | async init() { 58 | try { 59 | //@ts-ignore 60 | const nwls = await import("./nwl").catch((_) => undefined); 61 | if (nwls) { 62 | this.nwls.push(...nwls.default); 63 | } 64 | } catch (_) {} 65 | } 66 | 67 | async createWithdrawBody(params: WithdrawParams) { 68 | const cell = new Cell(); 69 | cell.bits.writeUint(0x5fcc3d14, 32); // transfer op 70 | cell.bits.writeUint(10, 64); 71 | cell.bits.writeAddress(new TonWeb.Address(this.options.burner)); // target address 72 | cell.bits.writeAddress(await this.getAddress()); // bridge as response address 73 | cell.bits.writeBit(false); // null custom_payload 74 | cell.bits.writeCoins(new BN(0)); // forward amount 75 | cell.bits.writeBit(true); // forward_payload in this slice, not separate cell 76 | 77 | const msg = new Cell(); 78 | msg.bits.writeUint(params.chainNonce, 8); 79 | msg.bits.writeUint(params.to.length, 16); 80 | msg.bits.writeBytes(params.to); 81 | msg.bits.writeBytes(new Uint8Array(12)); 82 | cell.refs[0] = msg; 83 | 84 | return cell; 85 | } 86 | 87 | async createFreezeBody(params: FreezeParams) { 88 | const cell = new Cell(); 89 | cell.bits.writeUint(0x5fcc3d14, 32); // transfer op 90 | cell.bits.writeUint(20, 64); 91 | cell.bits.writeAddress(await this.getAddress()); // target address 92 | cell.bits.writeAddress(await this.getAddress()); // undefined as response address 93 | cell.bits.writeBit(false); // null custom_payload 94 | cell.bits.writeCoins(params.amount || new BN(0)); 95 | cell.bits.writeBit(false); // forward_payload in this slice, not separate cell 96 | 97 | const payload = new Cell(); 98 | payload.bits.writeUint(params.chainNonce, 8); 99 | payload.bits.writeUint(params.to.length, 16); 100 | payload.bits.writeBytes(params.to); 101 | payload.bits.writeBytes(params.mintWith); 102 | cell.refs[0] = payload; 103 | return cell; 104 | } 105 | 106 | getPublicKey = async () => { 107 | const address = await this.getAddress(); 108 | const result = await this.provider.call2( 109 | address.toString(), 110 | "get_public_key" 111 | ); 112 | return result; 113 | }; 114 | 115 | isInitialized = async () => { 116 | const address = await this.getAddress(); 117 | const result = await this.provider.call2( 118 | address.toString(), 119 | "is_initialized" 120 | ); 121 | return result; 122 | }; 123 | 124 | getActionId = async () => { 125 | const address = await this.getAddress(); 126 | const result = await this.provider.call2( 127 | address.toString(), 128 | "get_action_id" 129 | ); 130 | return result; 131 | }; 132 | 133 | getWhitelist = async () => { 134 | if (this.whiteListedCollections.length) 135 | return this.whiteListedCollections.concat(this.nwls); 136 | 137 | const address = (await this.getAddress()).toString(); 138 | 139 | const readContract = async ( 140 | tries: number = 1 141 | ): Promise => { 142 | try { 143 | const res = await this.provider.call(address, "get_whitelist"); 144 | return res["stack"][0][1].bytes; 145 | } catch (e) { 146 | if (tries < 4) { 147 | return readContract(tries + 1); 148 | } 149 | return undefined; 150 | } 151 | }; 152 | 153 | const bytes = await readContract(); 154 | 155 | if (!bytes) { 156 | throw new Error("Could not load bridge contract state"); 157 | } 158 | 159 | try { 160 | const cell = CellF.fromBoc(Buffer.from(bytes, "base64")).at(0); 161 | 162 | const slice = Slice.fromCell(cell!); 163 | 164 | const whitelistedCollectionsMap = parseDictRefs(slice, 256); 165 | 166 | const whitelistedCollections = Array.from( 167 | whitelistedCollectionsMap.keys() 168 | ).map((collection) => 169 | Address.parseRaw( 170 | `0:${new BigNumber(collection).toString(16)}` 171 | ).toFriendly() 172 | ); 173 | 174 | if (!this.whiteListedCollections.length) { 175 | this.whiteListedCollections = whitelistedCollections; 176 | setTimeout(() => { 177 | (this.whiteListedCollections = []), 10_000; 178 | }); 179 | } 180 | 181 | return whitelistedCollections.concat(this.nwls); 182 | } catch (e: any) { 183 | console.log(e.message, "error when parsing whitelisted collectons"); 184 | throw new Error( 185 | e.message + "::error when parsing whitelisted collectons" 186 | ); 187 | } 188 | }; 189 | } 190 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./helpers/chain"; 2 | export * from "./helpers/elrond/elrond"; 3 | export * from "./helpers/evm/web3"; 4 | export * from "./helpers/tron"; 5 | export * from "./socket"; 6 | export * from "./services/emitter"; 7 | export * from "./factory"; 8 | export * from "./consts"; 9 | export * from "./config"; 10 | export { ChainNonce } from "./type-utils"; 11 | -------------------------------------------------------------------------------- /src/scripts/deploy_tron.ts: -------------------------------------------------------------------------------- 1 | import { baseTronHelperFactory } from "../helpers/tron"; 2 | //@ts-expect-error no typings, cope 3 | import TronWeb from "tronweb"; 4 | import { config } from "dotenv"; 5 | config(); 6 | 7 | (async () => { 8 | // Testnet 9 | // const api = "https://api.shasta.trongrid.io"; 10 | // const signer = process.env.TRON_SK!; 11 | // const xpnftWrappedUri = "https://bridge-wnftapi.herokuapp.com/w/"; 12 | // const xpnft1155WrappedUri = "https://bridge-wnftapi.herokuapp.com/w/{id}"; 13 | // const frostGroupKey = process.env.FROST_GROUP_KEY!; 14 | 15 | // Mainnet 16 | const api = "https://api.trongrid.io"; 17 | const signer = process.env.TRON_SK!; 18 | const xpnftWrappedUri = "https://wnfts.xp.network/w/"; 19 | const xpnft1155WrappedUri = "https://wnfts.xp.network/w/{id}"; 20 | const frostGroupKey = process.env.FROST_GROUP_KEY!; 21 | 22 | const prov = new TronWeb({ fullHost: api }); 23 | const tron = await baseTronHelperFactory(prov); 24 | 25 | const contracts = await tron.deployMinter( 26 | signer, 27 | frostGroupKey, 28 | xpnftWrappedUri, 29 | xpnft1155WrappedUri 30 | ); 31 | 32 | console.log(contracts); 33 | })().catch((e) => console.error(e)); 34 | -------------------------------------------------------------------------------- /src/services/emitter.ts: -------------------------------------------------------------------------------- 1 | export const Emitter = 2 | typeof window !== "undefined" ? new EventTarget() : undefined; 3 | -------------------------------------------------------------------------------- /src/services/estimator/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { ChainNonce } from "../../type-utils"; 3 | 4 | export interface SignatureService { 5 | getSignatureNear( 6 | from: ChainNonce, 7 | toChain: ChainNonce, 8 | nft: string, 9 | tokenContract: string, 10 | tokenId: string, 11 | to: string 12 | ): Promise; 13 | getSignatureDfinity( 14 | fc: ChainNonce, 15 | tc: ChainNonce, 16 | to: string, 17 | num: number 18 | ): Promise; 19 | casper( 20 | from: ChainNonce, 21 | to: ChainNonce, 22 | receiver: string, 23 | contract: string, 24 | token_id: string 25 | ): Promise; 26 | } 27 | 28 | interface SignatureServiceResponse { 29 | signature: string; 30 | fee: string; 31 | fees?: string; 32 | sig?: string; 33 | } 34 | 35 | export function signatureService(url: string): SignatureService { 36 | const signer = axios.create({ 37 | baseURL: url, 38 | }); 39 | return { 40 | async getSignatureNear( 41 | fromChain: ChainNonce, 42 | toChain: ChainNonce, 43 | _nft: string, 44 | tokenContract: string, 45 | tokenId: string, 46 | to: string 47 | ) { 48 | const result = await signer.post<{ 49 | data: SignatureServiceResponse; 50 | }>("/api/near/", { 51 | from: fromChain, 52 | to: toChain, 53 | receiver: to, 54 | nft: { 55 | token_id: tokenId, 56 | contract: tokenContract, 57 | }, 58 | }); 59 | console.log("near signature response", result); 60 | return result.data.data; 61 | }, 62 | async getSignatureDfinity(fc, tc, to, num: number) { 63 | const result = await signer.post<{ 64 | data: SignatureServiceResponse; 65 | }>("/api/dfinity/", { 66 | from: fc, 67 | to: tc, 68 | receiver: to, 69 | num, 70 | }); 71 | return result.data.data; 72 | }, 73 | async casper(from, to, receiver, contract, token_id) { 74 | const result = await signer.post<{ 75 | data: SignatureServiceResponse; 76 | }>("/api/casper/", { 77 | from, 78 | to, 79 | receiver, 80 | nft: { 81 | token_id, 82 | contract, 83 | }, 84 | }); 85 | return result.data.data; 86 | }, 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/services/exchangeRate/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BatchExchangeRateRepo, 3 | cachedExchangeRateRepo, 4 | ExchangeRateRepo, 5 | networkBatchExchangeRateRepo, 6 | NetworkModel, 7 | } from "crypto-exchange-rate"; 8 | 9 | export function exchangeRateRepo( 10 | baseUrl: string 11 | ): ExchangeRateRepo & BatchExchangeRateRepo { 12 | const baseService = NetworkModel.batchExchangeRateService(baseUrl); 13 | 14 | return cachedExchangeRateRepo( 15 | networkBatchExchangeRateRepo( 16 | baseService, 17 | NetworkModel.exchangeRateDtoMapper() 18 | ) 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/services/heartbeat/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { StatusResp } from "./resp"; 3 | 4 | export type BridgeHeartbeat = { 5 | status(): Promise; 6 | }; 7 | 8 | export function bridgeHeartbeat(baseURL: string): BridgeHeartbeat { 9 | const api = axios.create({ 10 | baseURL, 11 | }); 12 | 13 | return { 14 | async status() { 15 | const res = await api.get("/status"); 16 | return res.data; 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/services/heartbeat/resp.ts: -------------------------------------------------------------------------------- 1 | type DeathReason = { 2 | component: "node" | "validator" | "balance"; 3 | error: string; 4 | }; 5 | 6 | type ValidatorStatus = { 7 | status: "alive" | "dead"; 8 | death_reason?: DeathReason; 9 | }; 10 | 11 | type ChainStatus = { 12 | bridge_alive: boolean; 13 | validators: ValidatorStatus[]; 14 | }; 15 | 16 | export type StatusResp = { 17 | [chainNonce: string]: ChainStatus; 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/hederaApi.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export interface HederaService { 4 | getTokens(addresss: string): Promise<{ token_id: string; balance: number }[]>; 5 | getokenInfo(tokenId: string): Promise<{ treasury_account_id: string }>; 6 | isContract(address: string): Promise; 7 | getEVMAddress(address: string): Promise; 8 | readContract(to: string, data: any): Promise; 9 | getEVMAccount(address: string): Promise; 10 | getTranactionIdByHash(hash: string): Promise; 11 | } 12 | 13 | export function hederaService(url: string): HederaService { 14 | const request = axios.create({ 15 | baseURL: url, 16 | }); 17 | 18 | const getContract = async (address: string) => 19 | await request.get(`/contracts/${address}`).catch(() => undefined); 20 | 21 | return { 22 | async getTokens(address) { 23 | try { 24 | const res = (await request.get(`/accounts/${address}/tokens`)).data; 25 | return res.tokens; 26 | } catch { 27 | return []; 28 | } 29 | }, 30 | async getokenInfo(tokenId) { 31 | const res = (await request.get(`/tokens/${tokenId}`)).data; 32 | return res; 33 | }, 34 | async isContract(address) { 35 | const res = await getContract(address); 36 | return Boolean(res); 37 | }, 38 | async getEVMAddress(address) { 39 | const res = await getContract(address); 40 | if (res?.data?.evm_address) { 41 | return res.data.evm_address; 42 | } 43 | throw new Error("Failed to convert address to EVM format"); 44 | }, 45 | async getEVMAccount(address) { 46 | const res = await request.get(`/accounts/${address}`); 47 | if (res?.data?.evm_address) { 48 | return res.data.evm_address; 49 | } 50 | throw new Error("Failed to convert address to EVM format"); 51 | }, 52 | async readContract(to: string, data: any) { 53 | const res = await request.post(`/contracts/call`, { 54 | data, 55 | to, 56 | }); 57 | return res.data.result; 58 | }, 59 | async getTranactionIdByHash(hash) { 60 | const getTimestamp = (hash: string) => 61 | new Promise(async (resolve, reject) => { 62 | let timestamp: string | undefined; 63 | const tm = setTimeout( 64 | () => reject("Time out on getTimestapm "), 65 | 120 * 1000 66 | ); 67 | while (!timestamp) { 68 | await new Promise((r) => setTimeout(r, 3000)); 69 | 70 | const result = await request 71 | .get(`/contracts/results/${hash}`) 72 | .catch(() => undefined); 73 | timestamp = result?.data?.timestamp; 74 | } 75 | clearTimeout(tm); 76 | resolve(timestamp); 77 | }); 78 | 79 | const timestamp = await getTimestamp(hash); 80 | 81 | const error = new Error(`Failed to decode ${hash} to transactionId`); 82 | if (!timestamp) { 83 | throw error; 84 | } 85 | 86 | await new Promise((r) => setTimeout(r, 4000)); 87 | 88 | const transactions = await request 89 | .get(`/transactions?timestamp=${timestamp}`) 90 | .catch(() => undefined); 91 | 92 | const transaction = transactions?.data?.transactions?.at(0); 93 | if (!transaction?.transaction_id) { 94 | throw error; 95 | } 96 | 97 | return transaction.transaction_id; 98 | }, 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /src/services/multiversex.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { DepTrxData } from "../helpers/chain"; 3 | import { decodeBase64Array } from "../factory"; 4 | import { V3_ChainId } from "../type-utils"; 5 | 6 | export interface MultiversexService { 7 | getTokenInfo( 8 | collection: string, 9 | tokenId: string 10 | ): Promise<{ uris: string[]; royalties: number }>; 11 | getCollectionInfo(collection: string): Promise<{ name: string }>; 12 | getLockDecodedArgs(hash: string): Promise; 13 | } 14 | 15 | export function multiversexService( 16 | apiUrl: string, 17 | indexerUrl: string 18 | ): MultiversexService { 19 | const api = axios.create({ 20 | baseURL: apiUrl, 21 | }); 22 | 23 | const index = axios.create({ 24 | baseURL: indexerUrl, 25 | }); 26 | 27 | index; 28 | 29 | return { 30 | async getTokenInfo(collection, tokenId) { 31 | const nftData = ( 32 | await api(`/nfts/${collection + "-" + tokenId}`).catch(() => undefined) 33 | )?.data; 34 | 35 | if (!nftData) { 36 | throw new Error(`Failed to get ${tokenId} token data`); 37 | } 38 | 39 | return nftData; 40 | }, 41 | async getCollectionInfo(collection) { 42 | const collectionData = ( 43 | await api(`/collections/${collection}`).catch(() => undefined) 44 | )?.data; 45 | 46 | if (!collectionData) { 47 | throw new Error(`Failed to get ${collection} collection data`); 48 | } 49 | 50 | return collectionData; 51 | }, 52 | async getLockDecodedArgs(hash) { 53 | let event: any = undefined; 54 | let timedout = false; 55 | 56 | const tm1 = setTimeout(() => { 57 | timedout = true; 58 | }, 1000 * 60 * 3); 59 | 60 | while (!event && !timedout) { 61 | const res = await api.get(`/transactions/${hash}?withResults=true`); 62 | event = res?.data?.results 63 | ?.find((r: any) => r.logs) 64 | ?.logs?.events?.find((e: any) => e.identifier === "lock721"); 65 | 66 | !event && (await new Promise((r) => setTimeout(r, 10_000))); 67 | } 68 | 69 | if (!event) { 70 | throw new Error(`Failed to get ${hash} event logs`); 71 | } 72 | 73 | clearTimeout(tm1); 74 | 75 | const args = decodeBase64Array(event.topics); 76 | 77 | if (!args) { 78 | throw new Error(`Failed to decode ${hash} topics`); 79 | } 80 | 81 | return { 82 | tokenId: String(args[1].charCodeAt(0)), 83 | destinationChain: args[2] as V3_ChainId, 84 | destinationUserAddress: args[3], 85 | sourceNftContractAddress: args[4], 86 | tokenAmount: String(args[5].charCodeAt(0)), 87 | nftType: args[6] as "singular" | "multiple", 88 | sourceChain: args[7] as V3_ChainId, 89 | }; 90 | }, 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /src/services/nftList.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { ChainNonceGet, NftInfo, AppConfigs } from ".."; 3 | import { InferNativeNft } from "../type-utils"; 4 | 5 | export interface NftList { 6 | get( 7 | chain: ChainNonceGet, 8 | owner: string 9 | ): Promise>[]>; 10 | } 11 | 12 | export type NftListUtils = { 13 | getNftListAddr(address: string): string; 14 | }; 15 | 16 | export function nftList(url: string, nftListAuthToken: string): NftList { 17 | const nftlistRest = axios.create({ 18 | baseURL: url, 19 | headers: { 20 | Authorization: `Bearer ${nftListAuthToken}`, 21 | }, 22 | }); 23 | 24 | //if this is mainnet, than backup it with staging indexer 25 | const nftlistRestBackup = 26 | url === AppConfigs.MainNet().nftListUri && 27 | axios.create({ 28 | baseURL: AppConfigs.Staging().nftListUri, 29 | headers: { 30 | Authorization: `Bearer ${nftListAuthToken}`, 31 | }, 32 | }); 33 | return { 34 | async get(chain: ChainNonceGet & T & NftListUtils, owner: string) { 35 | if (chain.getNftListAddr) { 36 | owner = chain.getNftListAddr(owner); 37 | } 38 | 39 | let res = await nftlistRest 40 | .get<{ 41 | data: NftInfo>[]; 42 | }>(`/nfts/${chain.getNonce()}/${owner}`) 43 | .catch(async (e) => { 44 | if (!nftlistRestBackup) return e; 45 | return await nftlistRestBackup.get<{ 46 | data: NftInfo>[]; 47 | }>(`/nfts/${chain.getNonce()}/${owner}`); 48 | }); 49 | 50 | if (res.headers["Retry-After"]) { 51 | await new Promise((r) => setTimeout(r, 30000)); 52 | return await this.get(chain, owner); 53 | } 54 | return res.data.data; 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/services/notifier/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export type EvNotifier = ReturnType; 4 | 5 | type CollectionContractResponse = { 6 | contractAddress: string; 7 | collectionAddress: string; 8 | chainNonce: number; 9 | status: "SUCCESS" | "FAILED"; 10 | }; 11 | 12 | export function evNotifier(url: string) { 13 | const api = axios.create({ 14 | baseURL: url, 15 | }); 16 | 17 | return { 18 | async getCollectionContract(collectionAddress: string, chainNonce: number) { 19 | const res = ( 20 | await api 21 | .get( 22 | `/collection-contract/${collectionAddress}/${chainNonce}` 23 | ) 24 | .catch(() => ({ data: undefined })) 25 | ).data; 26 | 27 | if (res?.status === "SUCCESS") { 28 | return res.contractAddress; 29 | } 30 | 31 | return ""; 32 | }, 33 | async createCollectionContract( 34 | collectionAddress: string, 35 | chainNonce: number, 36 | type: string 37 | ) { 38 | //const ethereum = chainNonce === Chain.ETHEREUM; 39 | const error = new Error( 40 | "Failed to deploy contract. Please come back later" 41 | ); 42 | error.name = "FAIL"; 43 | const res = ( 44 | await api 45 | .post(`/collection-contract`, { 46 | collectionAddress, 47 | type, 48 | chainNonce, 49 | }) 50 | .catch(() => ({ data: undefined })) 51 | ).data; 52 | 53 | if (res?.status === "SUCCESS") { 54 | let contractAddress = res?.contractAddress || ""; 55 | 56 | let timedOut = false; 57 | const errorTimeout = setTimeout(() => { 58 | timedOut = true; 59 | }, 150_000); 60 | 61 | while (!contractAddress && !timedOut) { 62 | await new Promise((r) => setTimeout(r, 2_300)); 63 | contractAddress = await this.getCollectionContract( 64 | collectionAddress, 65 | chainNonce 66 | ); 67 | } 68 | clearTimeout(errorTimeout); 69 | if (timedOut && !contractAddress) throw error; 70 | 71 | return contractAddress; 72 | } 73 | 74 | throw error; 75 | }, 76 | async notifyWeb3( 77 | fromChain: number, 78 | fromHash: string, 79 | actionId?: string, 80 | type?: string, 81 | toChain?: number, 82 | txFees?: string, 83 | senderAddress?: string, 84 | targetAddress?: string, 85 | nftUri?: string, 86 | tokenId?: string, 87 | contract?: string 88 | ) { 89 | await api.post("/tx/web3", { 90 | chain_nonce: fromChain, 91 | tx_hash: fromHash, 92 | actionId, 93 | type, 94 | toChain, 95 | txFees, 96 | senderAddress, 97 | targetAddress, 98 | nftUri, 99 | tokenId, 100 | contract, 101 | }); 102 | }, 103 | async notifyTron(txHash: string) { 104 | await api.post("/tx/tron", { 105 | tx_hash: txHash, 106 | }); 107 | }, 108 | async notifyElrond( 109 | txHash: string, 110 | sender: string, 111 | uris: string[], 112 | action_id: string | undefined 113 | ) { 114 | await api.post("/tx/elrond", { 115 | tx_hash: txHash, 116 | sender, 117 | uris, 118 | action_id, 119 | }); 120 | }, 121 | async notifyTezos(txHash: string) { 122 | await api.post("/tx/tezos", { 123 | tx_hash: txHash, 124 | }); 125 | }, 126 | async notifyAlgorand(txHash: string) { 127 | await api.post("/tx/algorand", { 128 | tx_hash: txHash, 129 | }); 130 | }, 131 | async notifySecret(txHash: string, vk: string) { 132 | await api.post("/tx/scrt", { tx_hash: txHash, vk: vk }); 133 | }, 134 | async notifySolana(txHash: string) { 135 | await api.post("/tx/solana", { tx_hash: txHash }); 136 | }, 137 | async notifyNear(txHash: string) { 138 | await api.post("/tx/near", { tx_hash: txHash }); 139 | }, 140 | async notifyDfinity(actionId: string) { 141 | await api.post("/tx/dfinity", { action_id: actionId }); 142 | }, 143 | async notifyTon(txHash: string) { 144 | await api.post("/tx/ton", { tx_hash: txHash }); 145 | }, 146 | async notifyAptos(txHash: string) { 147 | await api.post("/tx/aptos", { tx_hash: txHash }); 148 | }, 149 | async notifyCasper(txHash: string) { 150 | await api.post("/tx/casper", { tx_hash: txHash }); 151 | }, 152 | async notifyEVM(nonce: number, address: string) { 153 | await api.post("/whitelist", { 154 | contract: address, 155 | chain_nonce: nonce, 156 | }); 157 | }, 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /src/services/scVerify.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, AxiosError } from "axios"; 2 | import { _headers } from ".."; 3 | 4 | import { FullChain } from ".."; 5 | 6 | export type ScVerifyUtils = { 7 | getScVerifyAddr(address: string): string; 8 | }; 9 | 10 | export interface ScVerifyService { 11 | checkWithOutTokenId( 12 | from: FullChain & ScVerifyUtils, 13 | chain: number, 14 | sc: string 15 | ): Promise; 16 | list(from: string, targetChain: number, fromChain: number): Promise; 17 | default( 18 | sc: string, 19 | chain: number, 20 | fromChain: number, 21 | tokenId: string | undefined 22 | ): Promise | undefined>; 23 | verify( 24 | from: string, 25 | to: string, 26 | targetChain: number, 27 | fromChain: number, 28 | tokenId?: string 29 | ): Promise | undefined>; 30 | } 31 | 32 | export function scVerify(url: string): ScVerifyService { 33 | const request = axios.create({ 34 | baseURL: url, 35 | }); 36 | return { 37 | async checkWithOutTokenId(from: any, chain: number, sc: string) { 38 | return ( 39 | await request 40 | .post("/default/checkWithOutTokenId", { 41 | fromChain: from.getNonce(), 42 | chain, 43 | sc: from.getScVerifyAddr ? from.getScVerifyAddr(sc) : sc, 44 | }) 45 | .catch(async (e: AxiosError) => { 46 | if ( 47 | (e.code == "404" || e.message.includes("404")) && 48 | from.getScVerifyAddr 49 | ) { 50 | return await request 51 | .post("/default/checkWithOutTokenId", { 52 | fromChain: from.getNonce(), 53 | chain, 54 | sc, 55 | }) 56 | .catch(() => ({ data: false })); 57 | } 58 | return { data: false }; 59 | }) 60 | ).data; 61 | }, 62 | 63 | async list(from: string, targetChain: number, fromChain: number) { 64 | const res = await request 65 | .get( 66 | `/verify/list?from=${from}&targetChain=${targetChain}&fromChain=${fromChain}&tokenId=1` 67 | ) 68 | .catch(() => ({ data: false })); 69 | 70 | if (res.data.data) return res.data.data.length > 0; 71 | 72 | return false; 73 | }, 74 | async verify( 75 | from: string, 76 | to: string, 77 | targetChain: number, 78 | fromChain: number, 79 | tokenId?: string 80 | ) { 81 | return await request 82 | .post( 83 | `/verify`, 84 | { from, to, targetChain, fromChain, tokenId }, 85 | { 86 | headers: _headers, 87 | } 88 | ) 89 | .catch(() => undefined); 90 | }, 91 | async default(sc, chain, fromChain, tokenId) { 92 | return await request 93 | .post( 94 | `/default/`, 95 | { 96 | sc, 97 | chain, 98 | fromChain, 99 | tokenId, 100 | }, 101 | { 102 | headers: _headers, 103 | } 104 | ) 105 | .catch(() => { 106 | return undefined; 107 | }); 108 | }, 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /src/services/whitelisted.ts: -------------------------------------------------------------------------------- 1 | import { AppConfig } from ".."; 2 | import axios, { AxiosInstance } from "axios"; 3 | 4 | export type WhitelistedService = AxiosInstance; 5 | 6 | export const whitelistedService = (appConfig: AppConfig) => { 7 | return axios.create({ 8 | baseURL: appConfig.whitelistedUri, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/socket.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { io, ManagerOptions, SocketOptions } from "socket.io-client"; 3 | import { ClaimNftInfo } from "./helpers/algorand"; 4 | 5 | type ChainAwaiter = { 6 | [action_id: string]: 7 | | { 8 | resolve?: (data: T) => void; 9 | event_res?: T; 10 | } 11 | | undefined; 12 | }; 13 | 14 | type SocketResInnerBuf = { 15 | [chain: number]: ChainAwaiter; 16 | }; 17 | 18 | type SocketResBuf = { 19 | getResolver( 20 | chain_id: number, 21 | action_id: string 22 | ): ((data: T) => void) | undefined; 23 | setResolver( 24 | chain_id: number, 25 | action_id: string, 26 | resolver: (data: T) => void 27 | ): void; 28 | getEventRes(chain_id: number, action_id: string): T | undefined; 29 | setEventRes(chain_id: number, action_id: string, res: T): void; 30 | unsetAction(chain_id: number, action_id: string): void; 31 | }; 32 | 33 | /** 34 | * Tracker for cross chain transaction 35 | */ 36 | export type TxnSocketHelper = { 37 | /** 38 | * 39 | * @param chain Nonce of the target chain 40 | * @param action_id Identifier for tracking a cross chain transaction 41 | * @returns transaction hash on the foreign chain 42 | */ 43 | waitTxHash(chain: number, action_id: string): Promise; 44 | }; 45 | 46 | export type AlgorandSocketHelper = { 47 | waitAlgorandNft( 48 | sourceChain: number, 49 | receiver: string, 50 | action_id: string 51 | ): Promise; 52 | claimNfts(receiver: string): Promise; 53 | claimHederaNfts(from: number, to: string): Promise; 54 | cleanNfts(owner: string): Promise; 55 | }; 56 | 57 | function pairAction(sourceChain: number, action_id: string): number { 58 | const numId = parseInt(action_id); 59 | return numId >= sourceChain 60 | ? numId * numId + sourceChain + numId 61 | : numId + sourceChain * sourceChain; 62 | } 63 | 64 | function socketResBuf(): SocketResBuf { 65 | const inner: SocketResInnerBuf = {}; 66 | 67 | const requireChain = (chain_id: number) => { 68 | if (inner[chain_id] === undefined) { 69 | inner[chain_id] = {}; 70 | } 71 | }; 72 | 73 | return { 74 | getResolver( 75 | chain_id: number, 76 | action_id: string 77 | ): ((data: T) => void) | undefined { 78 | requireChain(chain_id); 79 | 80 | return inner[chain_id][action_id]?.resolve; 81 | }, 82 | setResolver( 83 | chain_id: number, 84 | action_id: string, 85 | resolver: (data: T) => void 86 | ): void { 87 | requireChain(chain_id); 88 | 89 | inner[chain_id][action_id] = { resolve: resolver }; 90 | }, 91 | getEventRes(chain_id: number, action_id: string): T | undefined { 92 | requireChain(chain_id); 93 | 94 | return inner[chain_id][action_id]?.event_res; 95 | }, 96 | setEventRes(chain_id: number, action_id: string, res: T): void { 97 | requireChain(chain_id); 98 | 99 | inner[chain_id][action_id] = { event_res: res }; 100 | }, 101 | unsetAction(chain_id: number, action_id: string): void { 102 | requireChain(chain_id); 103 | 104 | inner[chain_id][action_id] = undefined; 105 | }, 106 | }; 107 | } 108 | 109 | function add_event( 110 | buf: SocketResBuf, 111 | chain: number, 112 | id: string, 113 | data: T 114 | ) { 115 | const resolve = buf.getResolver(chain, id); 116 | if (resolve === undefined) { 117 | buf.setEventRes(chain, id, data); 118 | return; 119 | } 120 | resolve(data); 121 | } 122 | 123 | async function waitSocketData( 124 | buf: SocketResBuf, 125 | chain: number, 126 | action_id: string 127 | ): Promise { 128 | const data = buf.getEventRes(chain, action_id); 129 | if (data !== undefined) { 130 | buf.unsetAction(chain, action_id); 131 | return data; 132 | } 133 | 134 | const dataP: Promise = new Promise((r) => { 135 | buf.setResolver(chain, action_id, r); 136 | }); 137 | 138 | return await dataP; 139 | } 140 | 141 | type DbClaimInfo = { 142 | receiver: string; 143 | app_id: string; 144 | nft_id: string; 145 | action_id: string; 146 | inserted_at: Date; 147 | }; 148 | 149 | /** 150 | * Create a [[SocketHelper]] 151 | * 152 | * @param uri URI of the Migration-Validator socket api 153 | * @param options socket.io options 154 | */ 155 | export function socketHelper( 156 | uri: string, 157 | options?: Partial 158 | ): TxnSocketHelper & AlgorandSocketHelper { 159 | const socket = io(uri, options); 160 | const txbuf: SocketResBuf = socketResBuf(); 161 | const algoBuf: SocketResBuf = socketResBuf(); 162 | const dbApi = axios.create({ 163 | baseURL: uri, 164 | }); 165 | 166 | socket.on( 167 | "tx_executed_event", 168 | (chain: number, action_id: string, hash: string) => { 169 | add_event(txbuf, chain, action_id, hash); 170 | } 171 | ); 172 | 173 | socket.on( 174 | "algorand_minted_event", 175 | (_: number, action_id: string, app_id: number, nft_id: number) => 176 | add_event(algoBuf, 15, action_id, { 177 | appId: app_id, 178 | nftId: nft_id, 179 | }) 180 | ); 181 | 182 | return { 183 | async waitTxHash(chain: number, action_id: string): Promise { 184 | return await waitSocketData(txbuf, chain, action_id); 185 | }, 186 | async waitAlgorandNft( 187 | sourceChain: number, 188 | receiver: string, 189 | action_id: string 190 | ): Promise { 191 | // Validator sends a an action paired with chain id 192 | // this is implementation dependent on validator 193 | const paired = pairAction(sourceChain, action_id).toString(); 194 | const dbData = await dbApi.get>( 195 | `/algorand_event/${receiver}/${paired}` 196 | ); 197 | if (dbData.data.app_id) { 198 | return { 199 | appId: parseInt(dbData.data.app_id!), 200 | nftId: parseInt(dbData.data.nft_id!), 201 | }; 202 | } 203 | 204 | return await waitSocketData(algoBuf, 15, paired); 205 | }, 206 | async claimNfts(receiver: string): Promise { 207 | const dbData = await dbApi.get<{ result: DbClaimInfo[] }>( 208 | `/algorand_event/${receiver}` 209 | ); 210 | return dbData.data.result; 211 | }, 212 | async claimHederaNfts(from, to) { 213 | const response = await dbApi.get(`/hedera_event/${from}/${to}`); 214 | return response.data; 215 | }, 216 | async cleanNfts(owner: string): Promise { 217 | await dbApi.delete(`/algorand_event/${owner}`); 218 | }, 219 | }; 220 | } 221 | -------------------------------------------------------------------------------- /src/type-utils.ts: -------------------------------------------------------------------------------- 1 | import { MetaMap, TransferNftForeign, v3_ChainId } from "."; 2 | 3 | type TransferNftChain = TransferNftForeign< 4 | Signer, 5 | RawNft, 6 | Resp 7 | >; 8 | 9 | export type ChainNonce = keyof MetaMap; 10 | export type V3_ChainId = `${v3_ChainId}`; 11 | 12 | export type InferChainParam = MetaMap[K][1]; 13 | export type InferChainH = MetaMap[K][0]; 14 | export type InferSigner = K extends TransferNftChain< 15 | infer S, 16 | unknown, 17 | unknown 18 | > 19 | ? S 20 | : never; 21 | 22 | export type InferNativeNft = K extends TransferNftChain< 23 | any, 24 | infer RawNft, 25 | any 26 | > 27 | ? RawNft 28 | : never; 29 | 30 | export type ParamMap = { 31 | set(k: T, v: InferChainParam | undefined): void; 32 | get(k: T): InferChainParam | undefined; 33 | }; 34 | 35 | export type HelperMap = Map< 36 | K, 37 | InferChainH | undefined 38 | >; 39 | 40 | export type Mutable = { 41 | -readonly [Key in keyof Type]: Type[Key]; 42 | }; 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": false, 4 | "target": "ES2022", 5 | "outDir": "./dist", 6 | //"outDir": "../bridge-interface/node_modules/xp.network/dist", //- to build directly in bridge ui 7 | //"outDir": "../bridge-tests/node_modules/xp.network/dist", 8 | "rootDir": "src", 9 | "moduleResolution": "node", 10 | "module": "commonjs", 11 | "declaration": true, 12 | "declarationMap": true, 13 | "inlineSourceMap": true, 14 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 15 | "resolveJsonModule": true /* Include modules imported with .json extension. */, 16 | "skipLibCheck": true, 17 | 18 | "strict": true /* Enable all strict type-checking options. */, 19 | 20 | /* Strict Type-Checking Options */ 21 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 22 | // "strictNullChecks": true /* Enable strict null checks. */, 23 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 24 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 25 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 26 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 27 | 28 | /* Additional Checks */ 29 | "noUnusedLocals": true /* Report errors on unused locals. */, 30 | "noUnusedParameters": true /* Report errors on unused parameters. */, 31 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 32 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 33 | 34 | /* Debugging Options */ 35 | "traceResolution": false /* Report module resolution log messages. */, 36 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 37 | "listFiles": false /* Print names of files part of the compilation. */, 38 | "pretty": true /* Stylize errors and messages using color and context. */, 39 | 40 | /* Experimental Options */ 41 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 42 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 43 | 44 | "lib": ["ES2021", "dom"], // elrdjs depends on dom? 45 | "types": ["node"], 46 | "typeRoots": ["node_modules/@types", "src/types"] 47 | }, 48 | "include": ["src/**/*.ts"], 49 | "exclude": ["node_modules/**"], 50 | "compileOnSave": false, 51 | "typedocOptions": { 52 | "entryPoints": ["src/index.ts"], 53 | "out": "docs" 54 | } 55 | } 56 | --------------------------------------------------------------------------------