├── .eslintrc.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── renovate.json └── workflows │ ├── cd.yaml │ ├── cf-deploy.yaml │ ├── ci-workflow.yaml │ └── pr.yaml ├── .gitignore ├── .nvmrc ├── .release-please-manifest.json ├── .yarn └── plugins │ └── @yarnpkg │ ├── plugin-engines.cjs │ ├── plugin-typescript.cjs │ └── plugin-workspace-tools.cjs ├── .yarnrc.yml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── package.json ├── packages ├── adapter │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── methods.ts │ │ ├── snap.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsconfig.json ├── example │ ├── .env.sample │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.tsx │ │ ├── Footer.tsx │ │ ├── chain_safe_logo.png │ │ ├── components │ │ │ ├── Account │ │ │ │ └── Account.tsx │ │ │ ├── SignMessage │ │ │ │ └── SignMessage.tsx │ │ │ ├── TransactionTable │ │ │ │ └── TransactionTable.tsx │ │ │ └── Transfer │ │ │ │ └── Transfer.tsx │ │ ├── containers │ │ │ ├── Dashboard │ │ │ │ └── Dashboard.tsx │ │ │ └── MetaMaskConnector │ │ │ │ └── MetaMaskConnector.tsx │ │ ├── context │ │ │ └── metamask.tsx │ │ ├── filecoin_logo.png │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── serviceWorker.ts │ │ ├── services │ │ │ ├── metamask.ts │ │ │ └── utils.ts │ │ ├── setupTests.ts │ │ └── style │ │ │ ├── index.scss │ │ │ ├── theme.ts │ │ │ └── variables.scss │ └── tsconfig.json ├── snap │ ├── .gitignore │ ├── @types │ │ ├── filecoin-signing-tools │ │ │ └── index.d.ts │ │ ├── lotus-client-provider-nodejs │ │ │ └── index.d.ts │ │ ├── lotus-client-rpc │ │ │ └── index.d.ts │ │ └── lotus-client-schema │ │ │ └── index.d.ts │ ├── CHANGELOG.md │ ├── index.html │ ├── package.json │ ├── post-process.js │ ├── snap.config.json │ ├── snap.manifest.json │ ├── src │ │ ├── configuration │ │ │ ├── index.ts │ │ │ └── predefined.ts │ │ ├── filecoin │ │ │ ├── account.ts │ │ │ ├── api.ts │ │ │ ├── message.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── rpc │ │ │ ├── configure.ts │ │ │ ├── estimateMessageGas.ts │ │ │ ├── exportPrivateKey.ts │ │ │ ├── getAddress.ts │ │ │ ├── getBalance.ts │ │ │ ├── getMessages.ts │ │ │ ├── getPublicKey.ts │ │ │ ├── sendMessage.ts │ │ │ └── signMessage.ts │ │ └── util │ │ │ ├── confirmation.ts │ │ │ ├── messageCreator.ts │ │ │ └── params.ts │ ├── test │ │ └── unit │ │ │ ├── configuration │ │ │ └── index.test.ts │ │ │ ├── filecoin │ │ │ ├── account.test.ts │ │ │ └── message.test.ts │ │ │ ├── lotusapi.mock.test.ts │ │ │ ├── rpc │ │ │ ├── configure.test.ts │ │ │ ├── exportSeed.test.ts │ │ │ ├── getAddress.test.ts │ │ │ ├── getBalance.test.ts │ │ │ ├── getPublicKey.test.ts │ │ │ ├── keyPairTestConstants.ts │ │ │ └── signMessage.test.ts │ │ │ └── wallet.mock.test.ts │ └── tsconfig.json └── types │ ├── CHANGELOG.md │ ├── index.d.ts │ └── package.json ├── release-please-config.json ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | require("@rushstack/eslint-patch/modern-module-resolution"); 2 | 3 | module.exports = { 4 | root: true, 5 | extends: "@chainsafe", 6 | }; -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @BeroBurny @irubido @mpetrunic -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: "Create a report to help us improve" 3 | title: "[bug]: " 4 | labels: ["bug", "unconfirmed"] 5 | assignees: 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the bug 11 | description: A clear and concise description of what the bug is. 12 | placeholder: After a pressing a button my pages freezes. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: reproduce 17 | attributes: 18 | label: To Reproduce 19 | description: Steps to reproduce the behavior. 20 | placeholder: | 21 | 1. Go to 22 | 2. Click on 23 | 3. Scroll down to 24 | 4. See error 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: logs 29 | attributes: 30 | label: Relevant log output 31 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 32 | render: shell 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: expected 37 | attributes: 38 | label: Expected behavior 39 | description: A clear and concise description of what you expected to happen. 40 | placeholder: When i click should work fine 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: context 45 | attributes: 46 | label: Additional context 47 | description: Add any other context about the problem here. 48 | validations: 49 | required: true 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: "Request new Feature to adapter/snap" 3 | title: "[feature]: " 4 | labels: ["enhancement", "unconfirmed"] 5 | assignees: 6 | body: 7 | - type: textarea 8 | id: summary 9 | attributes: 10 | label: Summary 11 | description: Summary of the context why you want to do this. 12 | placeholder: Tell us!!! 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: story 17 | attributes: 18 | label: Expected behavior / User Story 19 | description: Describe how you expect this improvement to behave once in production. 20 | placeholder: Users will feel safe... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: completion 25 | attributes: 26 | label: Completion 27 | placeholder: When i press that want that to do 28 | validations: 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "extends": [ 4 | "config:base", 5 | ":semanticCommitType(chore)" 6 | ], 7 | "packageRules": [{ 8 | "enabled": false, 9 | "groupName": "everything", 10 | "matchPackagePatterns": ["*"], 11 | "separateMajorMinor": false 12 | }], 13 | "rebaseWhen": "behind-base-branch", 14 | "vulnerabilityAlerts": { 15 | "enabled": true 16 | } 17 | } -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | maybe-release: 8 | name: release 9 | runs-on: ubuntu-latest 10 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 11 | steps: 12 | 13 | # you should probably do this after your regular CI checks passes 14 | - uses: google-github-actions/release-please-action@v3 # it will analyze commits and create PR with new version and updated CHANGELOG:md file. On merging it will create github release page with changelog 15 | id: release 16 | with: 17 | command: manifest 18 | token: ${{secrets.GITHUB_TOKEN}} 19 | default-branch: master 20 | release-type: node 21 | monorepo-tags: true 22 | changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":true}]' 23 | 24 | - uses: actions/checkout@v3 25 | # these if statements ensure that a publication only occurs when 26 | # a new release is created: 27 | if: ${{ steps.release.outputs.releases_created }} 28 | 29 | - uses: actions/setup-node@v3 30 | with: 31 | cache: 'yarn' 32 | node-version: 16 33 | registry-url: 'https://registry.npmjs.org' 34 | if: ${{ steps.release.outputs.releases_created }} 35 | 36 | - run: corepack enable 37 | 38 | - run: yarn install --immutable 39 | if: ${{ steps.release.outputs.releases_created }} 40 | 41 | - run: yarn build 42 | if: ${{ steps.release.outputs.releases_created }} 43 | 44 | - env: 45 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 46 | if: ${{ steps.release.outputs.releases_created }} 47 | run: | 48 | echo npmAuthToken: "$NODE_AUTH_TOKEN" >> ./.yarnrc.yml 49 | 50 | - run: yarn workspaces foreach -v --exclude root --no-private npm publish --tolerate-republish --access public 51 | if: ${{ steps.release.outputs.releases_created }} 52 | -------------------------------------------------------------------------------- /.github/workflows/cf-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: CloudFlare Deploy 2 | on: [push] 3 | 4 | jobs: 5 | deploy: 6 | runs-on: ubuntu-latest 7 | permissions: 8 | contents: read 9 | deployments: write 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | cache: yarn 15 | node-version: '16' 16 | - run: corepack enable 17 | - run: yarn install --immutable 18 | - run: yarn run build 19 | env: 20 | REACT_APP_SNAP_ID: npm:@chainsafe/filsnap 21 | - name: Publish to Cloudflare Pages 22 | uses: cloudflare/pages-action@1 23 | with: 24 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 25 | accountId: 2238a825c5aca59233eab1f221f7aefb 26 | projectName: filsnap 27 | directory: ./packages/example/build 28 | # Optional: Enable this if you want to have GitHub Deployments triggered 29 | gitHubToken: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | # This workflow is triggered on pushes to the repository. 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | build: 13 | # Job name is Greeting 14 | name: build 15 | # This job runs on Linux 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-node@v3 20 | with: 21 | cache: yarn 22 | node-version: '16.x' 23 | - run: corepack enable 24 | - run: yarn install --immutable 25 | - name: Run linter 26 | run: yarn run lint 27 | - name: Update Manifest for renovate 28 | if: github.event.pull_request.user.login == 'renovate' 29 | run: | 30 | yarn run manifest 31 | 32 | git config --global user.name "Renovate Bot" 33 | git config --global user.email "bot@renovateapp.com" 34 | 35 | git add -A 36 | git commit -m "chore: update metamask manifest" 37 | git push 38 | - name: Build 39 | run: yarn run build 40 | - uses: tj-actions/verify-changed-files@v9 41 | id: verify-changed-files 42 | with: 43 | files: | 44 | packages/snap/snap.manifest.json 45 | - name: Check if snap manifest updated 46 | if: steps.verify-changed-files.outputs.files_changed == 'true' 47 | run: | 48 | echo "Snap manifest not updated: ${{ steps.verify-changed-files.outputs.changed_files }}" 49 | exit 1 50 | - name: Run tests 51 | run: yarn run test 52 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: "Semantic PR" 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | - edited 7 | - synchronize 8 | jobs: 9 | main: 10 | name: Validate PR title 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: amannn/action-semantic-pull-request@v4 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | with: 17 | types: | 18 | fix 19 | feat 20 | chore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | .idea 4 | *.log 5 | node_modules 6 | .yarn/* 7 | !.yarn/patches 8 | !.yarn/plugins 9 | !.yarn/sdks 10 | !.yarn/versions -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/snap": "2.3.13", 3 | "packages/types": "2.1.3", 4 | "packages/adapter": "2.1.3" 5 | } -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-engines.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-engines", 5 | factory: function (require) { 6 | var plugin=(()=>{var R=Object.create,m=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var T=n=>m(n,"__esModule",{value:!0});var i=n=>{if(typeof require!="undefined")return require(n);throw new Error('Dynamic require of "'+n+'" is not supported')};var V=(n,e)=>{for(var r in e)m(n,r,{get:e[r],enumerable:!0})},N=(n,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of j(e))!Y.call(n,t)&&t!=="default"&&m(n,t,{get:()=>e[t],enumerable:!(r=P(e,t))||r.enumerable});return n},s=n=>N(T(m(n!=null?R(b(n)):{},"default",n&&n.__esModule&&"default"in n?{get:()=>n.default,enumerable:!0}:{value:n,enumerable:!0})),n);var S={};V(S,{default:()=>$});var o=s(i("@yarnpkg/core")),c;(function(r){r.Yarn="Yarn",r.Console="Console"})(c||(c={}));var h=class{constructor(e){this.throwWrongEngineError=(e,r)=>{let t=this.formatErrorMessage(e,r);this.throwError(t)};this.throwError=e=>{switch(this.errorReporter){case c.Yarn:this.reportYarnError(e);break;case c.Console:default:this.reportConsoleError(e);break}};this.reportYarnError=e=>{throw new o.ReportError(o.MessageName.UNNAMED,e)};this.reportConsoleError=e=>{console.error(e),process.exit(1)};this.formatErrorMessage=(e,r)=>{let{configuration:t}=this.project,a=o.formatUtils.applyStyle(t,o.formatUtils.pretty(t,this.engine,"green"),2),g=o.formatUtils.pretty(t,e,"cyan"),l=o.formatUtils.pretty(t,r,"cyan"),w=`The current ${a} version ${g} does not satisfy the required version ${l}.`;return o.formatUtils.pretty(t,w,"red")};this.project=e.project,this.errorReporter=e.errorReporter}};var f=s(i("fs")),u=s(i("path")),d=s(i("semver")),k=s(i("@yarnpkg/fslib")),p=s(i("@yarnpkg/core"));var v=class extends h{constructor(){super(...arguments);this.resolveNvmRequiredVersion=()=>{let{configuration:e,cwd:r}=this.project,t=(0,u.resolve)(k.npath.fromPortablePath(r),".nvmrc"),a=p.formatUtils.applyStyle(e,p.formatUtils.pretty(e,this.engine,"green"),2);if(!(0,f.existsSync)(t)){this.throwError(p.formatUtils.pretty(e,`Unable to verify the ${a} version. The .nvmrc file does not exist.`,"red"));return}let g=(0,f.readFileSync)(t,"utf-8").trim();if((0,d.validRange)(g))return g;let l=p.formatUtils.pretty(e,".nvmrc","yellow");this.throwError(p.formatUtils.pretty(e,`Unable to verify the ${a} version. The ${l} file contains an invalid semver range.`,"red"))}}get engine(){return"Node"}verifyEngine(e){let r=e.node;r!=null&&(r===".nvmrc"&&(r=this.resolveNvmRequiredVersion()),(0,d.satisfies)(process.version,r)||this.throwWrongEngineError(process.version.replace(/^v/i,""),r.replace(/^v/i,"")))}};var x=s(i("semver")),y=s(i("@yarnpkg/core"));var E=class extends h{get engine(){return"Yarn"}verifyEngine(e){let r=e.yarn;r!=null&&((0,x.satisfies)(y.YarnVersion,r)||this.throwWrongEngineError(y.YarnVersion,r))}};var C=n=>e=>{let{engines:r={}}=e.getWorkspaceByCwd(e.cwd).manifest.raw,t={project:e,errorReporter:n};[new v(t),new E(t)].forEach(g=>g.verifyEngine(r))},q={hooks:{validateProject:C(c.Yarn),setupScriptEnvironment:C(c.Console)}},$=q;return S;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-typescript.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-typescript", 5 | factory: function (require) { 6 | var plugin=(()=>{var Ft=Object.create,H=Object.defineProperty,Bt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,zt=Object.getOwnPropertyDescriptors,Gt=Object.getOwnPropertyNames,Q=Object.getOwnPropertySymbols,$t=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,De=Object.prototype.propertyIsEnumerable;var Re=(e,t,r)=>t in e?H(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,u=(e,t)=>{for(var r in t||(t={}))ne.call(t,r)&&Re(e,r,t[r]);if(Q)for(var r of Q(t))De.call(t,r)&&Re(e,r,t[r]);return e},g=(e,t)=>Bt(e,zt(t)),Lt=e=>H(e,"__esModule",{value:!0});var R=(e,t)=>{var r={};for(var s in e)ne.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(e!=null&&Q)for(var s of Q(e))t.indexOf(s)<0&&De.call(e,s)&&(r[s]=e[s]);return r};var I=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Vt=(e,t)=>{for(var r in t)H(e,r,{get:t[r],enumerable:!0})},Qt=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Gt(t))!ne.call(e,s)&&s!=="default"&&H(e,s,{get:()=>t[s],enumerable:!(r=Kt(t,s))||r.enumerable});return e},C=e=>Qt(Lt(H(e!=null?Ft($t(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var xe=I(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});function _(e){let t=[...e.caches],r=t.shift();return r===void 0?ve():{get(s,n,a={miss:()=>Promise.resolve()}){return r.get(s,n,a).catch(()=>_({caches:t}).get(s,n,a))},set(s,n){return r.set(s,n).catch(()=>_({caches:t}).set(s,n))},delete(s){return r.delete(s).catch(()=>_({caches:t}).delete(s))},clear(){return r.clear().catch(()=>_({caches:t}).clear())}}}function ve(){return{get(e,t,r={miss:()=>Promise.resolve()}){return t().then(n=>Promise.all([n,r.miss(n)])).then(([n])=>n)},set(e,t){return Promise.resolve(t)},delete(e){return Promise.resolve()},clear(){return Promise.resolve()}}}J.createFallbackableCache=_;J.createNullCache=ve});var Ee=I(($s,qe)=>{qe.exports=xe()});var Te=I(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});function Jt(e={serializable:!0}){let t={};return{get(r,s,n={miss:()=>Promise.resolve()}){let a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);let o=s(),d=n&&n.miss||(()=>Promise.resolve());return o.then(y=>d(y)).then(()=>o)},set(r,s){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete t[JSON.stringify(r)],Promise.resolve()},clear(){return t={},Promise.resolve()}}}ae.createInMemoryCache=Jt});var we=I((Vs,Me)=>{Me.exports=Te()});var Ce=I(M=>{"use strict";Object.defineProperty(M,"__esModule",{value:!0});function Xt(e,t,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers(){return e===oe.WithinHeaders?s:{}},queryParameters(){return e===oe.WithinQueryParameters?s:{}}}}function Yt(e){let t=0,r=()=>(t++,new Promise(s=>{setTimeout(()=>{s(e(r))},Math.min(100*t,1e3))}));return e(r)}function ke(e,t=(r,s)=>Promise.resolve()){return Object.assign(e,{wait(r){return ke(e.then(s=>Promise.all([t(s,r),s])).then(s=>s[1]))}})}function Zt(e){let t=e.length-1;for(t;t>0;t--){let r=Math.floor(Math.random()*(t+1)),s=e[t];e[t]=e[r],e[r]=s}return e}function er(e,t){return Object.keys(t!==void 0?t:{}).forEach(r=>{e[r]=t[r](e)}),e}function tr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}var rr="4.2.0",sr=e=>()=>e.transporter.requester.destroy(),oe={WithinQueryParameters:0,WithinHeaders:1};M.AuthMode=oe;M.addMethods=er;M.createAuth=Xt;M.createRetryablePromise=Yt;M.createWaitablePromise=ke;M.destroy=sr;M.encode=tr;M.shuffle=Zt;M.version=rr});var F=I((Js,Ue)=>{Ue.exports=Ce()});var Ne=I(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});var nr={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};ie.MethodEnum=nr});var B=I((Ys,We)=>{We.exports=Ne()});var Ze=I(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});var He=B();function ce(e,t){let r=e||{},s=r.data||{};return Object.keys(r).forEach(n=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(n)===-1&&(s[n]=r[n])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var X={Read:1,Write:2,Any:3},U={Up:1,Down:2,Timeouted:3},_e=2*60*1e3;function ue(e,t=U.Up){return g(u({},e),{status:t,lastUpdate:Date.now()})}function Fe(e){return e.status===U.Up||Date.now()-e.lastUpdate>_e}function Be(e){return e.status===U.Timeouted&&Date.now()-e.lastUpdate<=_e}function le(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||X.Any}}function ar(e,t){return Promise.all(t.map(r=>e.get(r,()=>Promise.resolve(ue(r))))).then(r=>{let s=r.filter(d=>Fe(d)),n=r.filter(d=>Be(d)),a=[...s,...n],o=a.length>0?a.map(d=>le(d)):t;return{getTimeout(d,y){return(n.length===0&&d===0?1:n.length+3+d)*y},statelessHosts:o}})}var or=({isTimedOut:e,status:t})=>!e&&~~t==0,ir=e=>{let t=e.status;return e.isTimedOut||or(e)||~~(t/100)!=2&&~~(t/100)!=4},cr=({status:e})=>~~(e/100)==2,ur=(e,t)=>ir(e)?t.onRetry(e):cr(e)?t.onSucess(e):t.onFail(e);function Qe(e,t,r,s){let n=[],a=$e(r,s),o=Le(e,s),d=r.method,y=r.method!==He.MethodEnum.Get?{}:u(u({},r.data),s.data),b=u(u(u({"x-algolia-agent":e.userAgent.value},e.queryParameters),y),s.queryParameters),f=0,p=(h,S)=>{let O=h.pop();if(O===void 0)throw Ve(de(n));let P={data:a,headers:o,method:d,url:Ge(O,r.path,b),connectTimeout:S(f,e.timeouts.connect),responseTimeout:S(f,s.timeout)},x=j=>{let T={request:P,response:j,host:O,triesLeft:h.length};return n.push(T),T},v={onSucess:j=>Ke(j),onRetry(j){let T=x(j);return j.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",pe(T)),e.hostsCache.set(O,ue(O,j.isTimedOut?U.Timeouted:U.Down))]).then(()=>p(h,S))},onFail(j){throw x(j),ze(j,de(n))}};return e.requester.send(P).then(j=>ur(j,v))};return ar(e.hostsCache,t).then(h=>p([...h.statelessHosts].reverse(),h.getTimeout))}function lr(e){let{hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,hosts:y,queryParameters:b,headers:f}=e,p={hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,headers:f,queryParameters:b,hosts:y.map(h=>le(h)),read(h,S){let O=ce(S,p.timeouts.read),P=()=>Qe(p,p.hosts.filter(j=>(j.accept&X.Read)!=0),h,O);if((O.cacheable!==void 0?O.cacheable:h.cacheable)!==!0)return P();let v={request:h,mappedRequestOptions:O,transporter:{queryParameters:p.queryParameters,headers:p.headers}};return p.responsesCache.get(v,()=>p.requestsCache.get(v,()=>p.requestsCache.set(v,P()).then(j=>Promise.all([p.requestsCache.delete(v),j]),j=>Promise.all([p.requestsCache.delete(v),Promise.reject(j)])).then(([j,T])=>T)),{miss:j=>p.responsesCache.set(v,j)})},write(h,S){return Qe(p,p.hosts.filter(O=>(O.accept&X.Write)!=0),h,ce(S,p.timeouts.write))}};return p}function dr(e){let t={value:`Algolia for JavaScript (${e})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return t.value.indexOf(s)===-1&&(t.value=`${t.value}${s}`),t}};return t}function Ke(e){try{return JSON.parse(e.content)}catch(t){throw Je(t.message,e)}}function ze({content:e,status:t},r){let s=e;try{s=JSON.parse(e).message}catch(n){}return Xe(s,t,r)}function pr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}function Ge(e,t,r){let s=Ye(r),n=`${e.protocol}://${e.url}/${t.charAt(0)==="/"?t.substr(1):t}`;return s.length&&(n+=`?${s}`),n}function Ye(e){let t=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(e).map(r=>pr("%s=%s",r,t(e[r])?JSON.stringify(e[r]):e[r])).join("&")}function $e(e,t){if(e.method===He.MethodEnum.Get||e.data===void 0&&t.data===void 0)return;let r=Array.isArray(e.data)?e.data:u(u({},e.data),t.data);return JSON.stringify(r)}function Le(e,t){let r=u(u({},e.headers),t.headers),s={};return Object.keys(r).forEach(n=>{let a=r[n];s[n.toLowerCase()]=a}),s}function de(e){return e.map(t=>pe(t))}function pe(e){let t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return g(u({},e),{request:g(u({},e.request),{headers:u(u({},e.request.headers),t)})})}function Xe(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}function Je(e,t){return{name:"DeserializationError",message:e,response:t}}function Ve(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}A.CallEnum=X;A.HostStatusEnum=U;A.createApiError=Xe;A.createDeserializationError=Je;A.createMappedRequestOptions=ce;A.createRetryError=Ve;A.createStatefulHost=ue;A.createStatelessHost=le;A.createTransporter=lr;A.createUserAgent=dr;A.deserializeFailure=ze;A.deserializeSuccess=Ke;A.isStatefulHostTimeouted=Be;A.isStatefulHostUp=Fe;A.serializeData=$e;A.serializeHeaders=Le;A.serializeQueryParameters=Ye;A.serializeUrl=Ge;A.stackFrameWithoutCredentials=pe;A.stackTraceWithoutCredentials=de});var K=I((en,et)=>{et.exports=Ze()});var tt=I(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});var N=F(),mr=K(),z=B(),hr=e=>{let t=e.region||"us",r=N.createAuth(N.AuthMode.WithinHeaders,e.appId,e.apiKey),s=mr.createTransporter(g(u({hosts:[{url:`analytics.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n=e.appId;return N.addMethods({appId:n,transporter:s},e.methods)},yr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:"2/abtests",data:t},r),gr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Delete,path:N.encode("2/abtests/%s",t)},r),fr=e=>(t,r)=>e.transporter.read({method:z.MethodEnum.Get,path:N.encode("2/abtests/%s",t)},r),br=e=>t=>e.transporter.read({method:z.MethodEnum.Get,path:"2/abtests"},t),Pr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:N.encode("2/abtests/%s/stop",t)},r);w.addABTest=yr;w.createAnalyticsClient=hr;w.deleteABTest=gr;w.getABTest=fr;w.getABTests=br;w.stopABTest=Pr});var st=I((rn,rt)=>{rt.exports=tt()});var at=I(G=>{"use strict";Object.defineProperty(G,"__esModule",{value:!0});var me=F(),jr=K(),nt=B(),Or=e=>{let t=e.region||"us",r=me.createAuth(me.AuthMode.WithinHeaders,e.appId,e.apiKey),s=jr.createTransporter(g(u({hosts:[{url:`recommendation.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)}));return me.addMethods({appId:e.appId,transporter:s},e.methods)},Ir=e=>t=>e.transporter.read({method:nt.MethodEnum.Get,path:"1/strategies/personalization"},t),Ar=e=>(t,r)=>e.transporter.write({method:nt.MethodEnum.Post,path:"1/strategies/personalization",data:t},r);G.createRecommendationClient=Or;G.getPersonalizationStrategy=Ir;G.setPersonalizationStrategy=Ar});var it=I((nn,ot)=>{ot.exports=at()});var jt=I(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});var l=F(),q=K(),m=B(),Sr=require("crypto");function Y(e){let t=r=>e.request(r).then(s=>{if(e.batch!==void 0&&e.batch(s.hits),!e.shouldStop(s))return s.cursor?t({cursor:s.cursor}):t({page:(r.page||0)+1})});return t({})}var Dr=e=>{let t=e.appId,r=l.createAuth(e.authMode!==void 0?e.authMode:l.AuthMode.WithinHeaders,t,e.apiKey),s=q.createTransporter(g(u({hosts:[{url:`${t}-dsn.algolia.net`,accept:q.CallEnum.Read},{url:`${t}.algolia.net`,accept:q.CallEnum.Write}].concat(l.shuffle([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}]))},e),{headers:u(g(u({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n={transporter:s,appId:t,addAlgoliaAgent(a,o){s.userAgent.add({segment:a,version:o})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return l.addMethods(n,e.methods)};function ct(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ut(){return{name:"ObjectNotFoundError",message:"Object not found."}}function lt(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Rr=e=>(t,r)=>{let d=r||{},{queryParameters:s}=d,n=R(d,["queryParameters"]),a=u({acl:t},s!==void 0?{queryParameters:s}:{}),o=(y,b)=>l.createRetryablePromise(f=>$(e)(y.key,b).catch(p=>{if(p.status!==404)throw p;return f()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/keys",data:a},n),o)},vr=e=>(t,r,s)=>{let n=q.createMappedRequestOptions(s);return n.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},n)},xr=e=>(t,r,s)=>e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:r}},s),Z=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"copy",destination:r}},s),n)},qr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Rules]})),Er=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Settings]})),Tr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Synonyms]})),Mr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).then(o).catch(d=>{if(d.status!==404)throw d}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/keys/%s",t)},r),s)},wr=()=>(e,t)=>{let r=q.serializeQueryParameters(t),s=Sr.createHmac("sha256",e).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},$=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/keys/%s",t)},r),kr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/logs"},t),Cr=()=>e=>{let t=Buffer.from(e,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=t.match(r);if(s===null)throw lt();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Ur=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/top"},t),Nr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/clusters/mapping/%s",t)},r),Wr=e=>t=>{let n=t||{},{retrieveMappings:r}=n,s=R(n,["retrieveMappings"]);return r===!0&&(s.getClusters=!0),e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},L=e=>(t,r={})=>{let s={transporter:e.transporter,appId:e.appId,indexName:t};return l.addMethods(s,r.methods)},Hr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/keys"},t),_r=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters"},t),Fr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/indexes"},t),Br=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping"},t),Kr=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"move",destination:r}},s),n)},zr=e=>(t,r)=>{let s=(n,a)=>Promise.all(Object.keys(n.taskID).map(o=>L(e)(o,{methods:{waitTask:D}}).waitTask(n.taskID[o],a)));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:t}},r),s)},Gr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:t}},r),$r=e=>(t,r)=>{let s=t.map(n=>g(u({},n),{params:q.serializeQueryParameters(n.params||{})}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Lr=e=>(t,r)=>Promise.all(t.map(s=>{let d=s.params,{facetName:n,facetQuery:a}=d,o=R(d,["facetName","facetQuery"]);return L(e)(s.indexName,{methods:{searchForFacetValues:dt}}).searchForFacetValues(n,a,u(u({},r),o))})),Vr=e=>(t,r)=>{let s=q.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Qr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).catch(d=>{if(d.status!==404)throw d;return o()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/keys/%s/restore",t)},r),s)},Jr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:t}},r),Xr=e=>(t,r)=>{let s=Object.assign({},r),f=r||{},{queryParameters:n}=f,a=R(f,["queryParameters"]),o=n?{queryParameters:n}:{},d=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],y=p=>Object.keys(s).filter(h=>d.indexOf(h)!==-1).every(h=>p[h]===s[h]),b=(p,h)=>l.createRetryablePromise(S=>$(e)(t,h).then(O=>y(O)?Promise.resolve():S()));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/keys/%s",t),data:o},a),b)},pt=e=>(t,r)=>{let s=(n,a)=>D(e)(n.taskID,a);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/batch",e.indexName),data:{requests:t}},r),s)},Yr=e=>t=>Y(g(u({},t),{shouldStop:r=>r.cursor===void 0,request:r=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/browse",e.indexName),data:r},t)})),Zr=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},es=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},te=e=>(t,r,s)=>{let y=s||{},{batchSize:n}=y,a=R(y,["batchSize"]),o={taskIDs:[],objectIDs:[]},d=(b=0)=>{let f=[],p;for(p=b;p({action:r,body:h})),a).then(h=>(o.objectIDs=o.objectIDs.concat(h.objectIDs),o.taskIDs.push(h.taskID),p++,d(p)))};return l.createWaitablePromise(d(),(b,f)=>Promise.all(b.taskIDs.map(p=>D(e)(p,f))))},ts=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/clear",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),rs=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ss=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ns=e=>(t,r)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/deleteByQuery",e.indexName),data:t},r),(s,n)=>D(e)(s.taskID,n)),as=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),os=e=>(t,r)=>l.createWaitablePromise(yt(e)([t],r).then(s=>({taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),yt=e=>(t,r)=>{let s=t.map(n=>({objectID:n}));return te(e)(s,k.DeleteObject,r)},is=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},cs=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},us=e=>t=>gt(e)(t).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),ls=e=>(t,r)=>{let y=r||{},{query:s,paginate:n}=y,a=R(y,["query","paginate"]),o=0,d=()=>ft(e)(s||"",g(u({},a),{page:o})).then(b=>{for(let[f,p]of Object.entries(b.hits))if(t(p))return{object:p,position:parseInt(f,10),page:o};if(o++,n===!1||o>=b.nbPages)throw ut();return d()});return d()},ds=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/%s",e.indexName,t)},r),ps=()=>(e,t)=>{for(let[r,s]of Object.entries(e.hits))if(s.objectID===t)return parseInt(r,10);return-1},ms=e=>(t,r)=>{let o=r||{},{attributesToRetrieve:s}=o,n=R(o,["attributesToRetrieve"]),a=t.map(d=>u({indexName:e.indexName,objectID:d},s?{attributesToRetrieve:s}:{}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:a}},n)},hs=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},r),gt=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),ys=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},r),bt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/task/%s",e.indexName,t.toString())},r),gs=e=>(t,r)=>l.createWaitablePromise(Pt(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),Pt=e=>(t,r)=>{let o=r||{},{createIfNotExists:s}=o,n=R(o,["createIfNotExists"]),a=s?k.PartialUpdateObject:k.PartialUpdateObjectNoCreate;return te(e)(t,a,n)},fs=e=>(t,r)=>{let O=r||{},{safe:s,autoGenerateObjectIDIfNotExist:n,batchSize:a}=O,o=R(O,["safe","autoGenerateObjectIDIfNotExist","batchSize"]),d=(P,x,v,j)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",P),data:{operation:v,destination:x}},j),(T,V)=>D(e)(T.taskID,V)),y=Math.random().toString(36).substring(7),b=`${e.indexName}_tmp_${y}`,f=he({appId:e.appId,transporter:e.transporter,indexName:b}),p=[],h=d(e.indexName,b,"copy",g(u({},o),{scope:["settings","synonyms","rules"]}));p.push(h);let S=(s?h.wait(o):h).then(()=>{let P=f(t,g(u({},o),{autoGenerateObjectIDIfNotExist:n,batchSize:a}));return p.push(P),s?P.wait(o):P}).then(()=>{let P=d(b,e.indexName,"move",o);return p.push(P),s?P.wait(o):P}).then(()=>Promise.all(p)).then(([P,x,v])=>({objectIDs:x.objectIDs,taskIDs:[P.taskID,...x.taskIDs,v.taskID]}));return l.createWaitablePromise(S,(P,x)=>Promise.all(p.map(v=>v.wait(x))))},bs=e=>(t,r)=>ye(e)(t,g(u({},r),{clearExistingRules:!0})),Ps=e=>(t,r)=>ge(e)(t,g(u({},r),{replaceExistingSynonyms:!0})),js=e=>(t,r)=>l.createWaitablePromise(he(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),he=e=>(t,r)=>{let o=r||{},{autoGenerateObjectIDIfNotExist:s}=o,n=R(o,["autoGenerateObjectIDIfNotExist"]),a=s?k.AddObject:k.UpdateObject;if(a===k.UpdateObject){for(let d of t)if(d.objectID===void 0)return l.createWaitablePromise(Promise.reject(ct()))}return te(e)(t,a,n)},Os=e=>(t,r)=>ye(e)([t],r),ye=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,clearExistingRules:n}=d,a=R(d,["forwardToReplicas","clearExistingRules"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.clearExistingRules=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},Is=e=>(t,r)=>ge(e)([t],r),ge=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,replaceExistingSynonyms:n}=d,a=R(d,["forwardToReplicas","replaceExistingSynonyms"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.replaceExistingSynonyms=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},ft=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),dt=e=>(t,r,s)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},s),mt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/search",e.indexName),data:{query:t}},r),ht=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},r),As=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/indexes/%s/settings",e.indexName),data:t},a),(d,y)=>D(e)(d.taskID,y))},D=e=>(t,r)=>l.createRetryablePromise(s=>bt(e)(t,r).then(n=>n.status!=="published"?s():void 0)),Ss={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},k={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},ee={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Ds={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Rs={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};i.ApiKeyACLEnum=Ss;i.BatchActionEnum=k;i.ScopeEnum=ee;i.StrategyEnum=Ds;i.SynonymEnum=Rs;i.addApiKey=Rr;i.assignUserID=vr;i.assignUserIDs=xr;i.batch=pt;i.browseObjects=Yr;i.browseRules=Zr;i.browseSynonyms=es;i.chunkedBatch=te;i.clearObjects=ts;i.clearRules=rs;i.clearSynonyms=ss;i.copyIndex=Z;i.copyRules=qr;i.copySettings=Er;i.copySynonyms=Tr;i.createBrowsablePromise=Y;i.createMissingObjectIDError=ct;i.createObjectNotFoundError=ut;i.createSearchClient=Dr;i.createValidUntilNotFoundError=lt;i.deleteApiKey=Mr;i.deleteBy=ns;i.deleteIndex=as;i.deleteObject=os;i.deleteObjects=yt;i.deleteRule=is;i.deleteSynonym=cs;i.exists=us;i.findObject=ls;i.generateSecuredApiKey=wr;i.getApiKey=$;i.getLogs=kr;i.getObject=ds;i.getObjectPosition=ps;i.getObjects=ms;i.getRule=hs;i.getSecuredApiKeyRemainingValidity=Cr;i.getSettings=gt;i.getSynonym=ys;i.getTask=bt;i.getTopUserIDs=Ur;i.getUserID=Nr;i.hasPendingMappings=Wr;i.initIndex=L;i.listApiKeys=Hr;i.listClusters=_r;i.listIndices=Fr;i.listUserIDs=Br;i.moveIndex=Kr;i.multipleBatch=zr;i.multipleGetObjects=Gr;i.multipleQueries=$r;i.multipleSearchForFacetValues=Lr;i.partialUpdateObject=gs;i.partialUpdateObjects=Pt;i.removeUserID=Vr;i.replaceAllObjects=fs;i.replaceAllRules=bs;i.replaceAllSynonyms=Ps;i.restoreApiKey=Qr;i.saveObject=js;i.saveObjects=he;i.saveRule=Os;i.saveRules=ye;i.saveSynonym=Is;i.saveSynonyms=ge;i.search=ft;i.searchForFacetValues=dt;i.searchRules=mt;i.searchSynonyms=ht;i.searchUserIDs=Jr;i.setSettings=As;i.updateApiKey=Xr;i.waitTask=D});var It=I((on,Ot)=>{Ot.exports=jt()});var At=I(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});function vs(){return{debug(e,t){return Promise.resolve()},info(e,t){return Promise.resolve()},error(e,t){return Promise.resolve()}}}var xs={Debug:1,Info:2,Error:3};re.LogLevelEnum=xs;re.createNullLogger=vs});var Dt=I((un,St)=>{St.exports=At()});var xt=I(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});var Rt=require("http"),vt=require("https"),qs=require("url");function Es(){let e={keepAlive:!0},t=new Rt.Agent(e),r=new vt.Agent(e);return{send(s){return new Promise(n=>{let a=qs.parse(s.url),o=a.query===null?a.pathname:`${a.pathname}?${a.query}`,d=u({agent:a.protocol==="https:"?r:t,hostname:a.hostname,path:o,method:s.method,headers:s.headers},a.port!==void 0?{port:a.port||""}:{}),y=(a.protocol==="https:"?vt:Rt).request(d,h=>{let S="";h.on("data",O=>S+=O),h.on("end",()=>{clearTimeout(f),clearTimeout(p),n({status:h.statusCode||0,content:S,isTimedOut:!1})})}),b=(h,S)=>setTimeout(()=>{y.abort(),n({status:0,content:S,isTimedOut:!0})},h*1e3),f=b(s.connectTimeout,"Connection timeout"),p;y.on("error",h=>{clearTimeout(f),clearTimeout(p),n({status:0,content:h.message,isTimedOut:!1})}),y.once("response",()=>{clearTimeout(f),p=b(s.responseTimeout,"Socket timeout")}),s.data!==void 0&&y.write(s.data),y.end()})},destroy(){return t.destroy(),r.destroy(),Promise.resolve()}}}fe.createNodeHttpRequester=Es});var Et=I((dn,qt)=>{qt.exports=xt()});var kt=I((pn,Tt)=>{"use strict";var Mt=Ee(),Ts=we(),W=st(),be=F(),Pe=it(),c=It(),Ms=Dt(),ws=Et(),ks=K();function wt(e,t,r){let s={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:ws.createNodeHttpRequester(),logger:Ms.createNullLogger(),responsesCache:Mt.createNullCache(),requestsCache:Mt.createNullCache(),hostsCache:Ts.createInMemoryCache(),userAgent:ks.createUserAgent(be.version).add({segment:"Node.js",version:process.versions.node})};return c.createSearchClient(g(u(u({},s),r),{methods:{search:c.multipleQueries,searchForFacetValues:c.multipleSearchForFacetValues,multipleBatch:c.multipleBatch,multipleGetObjects:c.multipleGetObjects,multipleQueries:c.multipleQueries,copyIndex:c.copyIndex,copySettings:c.copySettings,copyRules:c.copyRules,copySynonyms:c.copySynonyms,moveIndex:c.moveIndex,listIndices:c.listIndices,getLogs:c.getLogs,listClusters:c.listClusters,multipleSearchForFacetValues:c.multipleSearchForFacetValues,getApiKey:c.getApiKey,addApiKey:c.addApiKey,listApiKeys:c.listApiKeys,updateApiKey:c.updateApiKey,deleteApiKey:c.deleteApiKey,restoreApiKey:c.restoreApiKey,assignUserID:c.assignUserID,assignUserIDs:c.assignUserIDs,getUserID:c.getUserID,searchUserIDs:c.searchUserIDs,listUserIDs:c.listUserIDs,getTopUserIDs:c.getTopUserIDs,removeUserID:c.removeUserID,hasPendingMappings:c.hasPendingMappings,generateSecuredApiKey:c.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:c.getSecuredApiKeyRemainingValidity,destroy:be.destroy,initIndex:n=>a=>c.initIndex(n)(a,{methods:{batch:c.batch,delete:c.deleteIndex,getObject:c.getObject,getObjects:c.getObjects,saveObject:c.saveObject,saveObjects:c.saveObjects,search:c.search,searchForFacetValues:c.searchForFacetValues,waitTask:c.waitTask,setSettings:c.setSettings,getSettings:c.getSettings,partialUpdateObject:c.partialUpdateObject,partialUpdateObjects:c.partialUpdateObjects,deleteObject:c.deleteObject,deleteObjects:c.deleteObjects,deleteBy:c.deleteBy,clearObjects:c.clearObjects,browseObjects:c.browseObjects,getObjectPosition:c.getObjectPosition,findObject:c.findObject,exists:c.exists,saveSynonym:c.saveSynonym,saveSynonyms:c.saveSynonyms,getSynonym:c.getSynonym,searchSynonyms:c.searchSynonyms,browseSynonyms:c.browseSynonyms,deleteSynonym:c.deleteSynonym,clearSynonyms:c.clearSynonyms,replaceAllObjects:c.replaceAllObjects,replaceAllSynonyms:c.replaceAllSynonyms,searchRules:c.searchRules,getRule:c.getRule,deleteRule:c.deleteRule,saveRule:c.saveRule,saveRules:c.saveRules,replaceAllRules:c.replaceAllRules,browseRules:c.browseRules,clearRules:c.clearRules}}),initAnalytics:()=>n=>W.createAnalyticsClient(g(u(u({},s),n),{methods:{addABTest:W.addABTest,getABTest:W.getABTest,getABTests:W.getABTests,stopABTest:W.stopABTest,deleteABTest:W.deleteABTest}})),initRecommendation:()=>n=>Pe.createRecommendationClient(g(u(u({},s),n),{methods:{getPersonalizationStrategy:Pe.getPersonalizationStrategy,setPersonalizationStrategy:Pe.setPersonalizationStrategy}}))}}))}wt.version=be.version;Tt.exports=wt});var Ut=I((mn,je)=>{var Ct=kt();je.exports=Ct;je.exports.default=Ct});var Ws={};Vt(Ws,{default:()=>Ks});var Oe=C(require("@yarnpkg/core")),E=C(require("@yarnpkg/core")),Ie=C(require("@yarnpkg/plugin-essentials")),Ht=C(require("semver"));var se=C(require("@yarnpkg/core")),Nt=C(Ut()),Cs="e8e1bd300d860104bb8c58453ffa1eb4",Us="OFCNCOG2CU",Wt=async(e,t)=>{var a;let r=se.structUtils.stringifyIdent(e),n=Ns(t).initIndex("npm-search");try{return((a=(await n.getObject(r,{attributesToRetrieve:["types"]})).types)==null?void 0:a.ts)==="definitely-typed"}catch(o){return!1}},Ns=e=>(0,Nt.default)(Us,Cs,{requester:{async send(r){try{let s=await se.httpUtils.request(r.url,r.data||null,{configuration:e,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var _t=e=>e.scope?`${e.scope}__${e.name}`:`${e.name}`,Hs=async(e,t,r,s)=>{if(r.scope==="types")return;let{project:n}=e,{configuration:a}=n,o=a.makeResolver(),d={project:n,resolver:o,report:new E.ThrowReport};if(!await Wt(r,a))return;let b=_t(r),f=E.structUtils.parseRange(r.range).selector;if(!E.semverUtils.validRange(f)){let P=await o.getCandidates(r,new Map,d);f=E.structUtils.parseRange(P[0].reference).selector}let p=Ht.default.coerce(f);if(p===null)return;let h=`${Ie.suggestUtils.Modifier.CARET}${p.major}`,S=E.structUtils.makeDescriptor(E.structUtils.makeIdent("types",b),h),O=E.miscUtils.mapAndFind(n.workspaces,P=>{var T,V;let x=(T=P.manifest.dependencies.get(r.identHash))==null?void 0:T.descriptorHash,v=(V=P.manifest.devDependencies.get(r.identHash))==null?void 0:V.descriptorHash;if(x!==r.descriptorHash&&v!==r.descriptorHash)return E.miscUtils.mapAndFind.skip;let j=[];for(let Ae of Oe.Manifest.allDependencies){let Se=P.manifest[Ae].get(S.identHash);typeof Se!="undefined"&&j.push([Ae,Se])}return j.length===0?E.miscUtils.mapAndFind.skip:j});if(typeof O!="undefined")for(let[P,x]of O)e.manifest[P].set(x.identHash,x);else{try{if((await o.getCandidates(S,new Map,d)).length===0)return}catch{return}e.manifest[Ie.suggestUtils.Target.DEVELOPMENT].set(S.identHash,S)}},_s=async(e,t,r)=>{if(r.scope==="types")return;let s=_t(r),n=E.structUtils.makeIdent("types",s);for(let a of Oe.Manifest.allDependencies)typeof e.manifest[a].get(n.identHash)!="undefined"&&e.manifest[a].delete(n.identHash)},Fs=(e,t)=>{t.publishConfig&&t.publishConfig.typings&&(t.typings=t.publishConfig.typings),t.publishConfig&&t.publishConfig.types&&(t.types=t.publishConfig.types)},Bs={hooks:{afterWorkspaceDependencyAddition:Hs,afterWorkspaceDependencyRemoval:_s,beforeWorkspacePacking:Fs}},Ks=Bs;return Ws;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | # size of cache is not concern if you aren't using zero-installs 2 | compressionLevel: 0 3 | 4 | enableGlobalCache: true 5 | 6 | nmMode: hardlinks-local 7 | 8 | nodeLinker: node-modules 9 | 10 | plugins: 11 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 12 | spec: "@yarnpkg/plugin-typescript" 13 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 14 | spec: "@yarnpkg/plugin-workspace-tools" 15 | - path: .yarn/plugins/@yarnpkg/plugin-engines.cjs 16 | spec: "https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js" 17 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright [2020] [Node Factory d.o.o.] 179 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Node Factory d.o.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repo is not maintained. See latest development at https://github.com/filecoin-project/filsnap 2 | 3 | # FilSnap 4 | ![](https://github.com/chainsafe/filsnap/workflows/ci/badge.svg) 5 | ![](https://img.shields.io/badge/yarn-%3E%3D3-orange) 6 | ![](https://img.shields.io/badge/Node.js-%3E%3D16-orange) 7 | 8 | Snap to enable MetaMask users interaction with filecoin dapps. For detailed documentation and integration instructions see our [wiki](https://github.com/chainsafe/filsnap/wiki). 9 | 10 | ## Testing FilSnap 11 | 12 | ### MetaMask Flask 13 | Snaps is pre-release software available in MetaMask Flask, a canary distribution for developers that provides access to upcoming features. To try Snaps [install MetaMask Flask](https://metamask.io/flask/). 14 | 15 | ### Live demo dapp 16 | Test FilSnap inside [our demo dapp](http://filsnap.chainsafe.io/). 17 | 18 | ## Development 19 | 20 | ### Requirements 21 | ``` 22 | node version 16 or above 23 | ``` 24 | 25 | ### Usage 26 | * For nvm users 27 | ```sh 28 | nvm use 29 | ``` 30 | --- 31 | * Enable corepack 32 | ```sh 33 | corepack enable 34 | ``` 35 | * Install packages 36 | ```sh 37 | yarn install 38 | ``` 39 | * Run local snap server and React Demo page 40 | ```sh 41 | yarn demo 42 | ``` 43 | 44 | ## License 45 | This project is dual-licensed under Apache 2.0 and MIT terms: 46 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 47 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "author": "ChainSafe ", 5 | "license": "(Apache-2.0 AND MIT)", 6 | "packageManager": "yarn@3.4.1", 7 | "workspaces": [ 8 | "packages/*" 9 | ], 10 | "engines": { 11 | "node": ">=16.0.0" 12 | }, 13 | "scripts": { 14 | "manifest": "yarn workspace @chainsafe/filsnap manifest", 15 | "build:snap": "yarn workspace @chainsafe/filsnap build", 16 | "build:adapter": "yarn workspace @chainsafe/filsnap-adapter build", 17 | "start:snap": "yarn workspace @chainsafe/filsnap serve", 18 | "start:example": "REACT_APP_SNAP=local yarn workspace example start", 19 | "predemo": "yarn run build:snap && yarn run build:adapter", 20 | "build": "yarn workspaces foreach -vpt run build", 21 | "test": "yarn workspaces foreach -vp run test", 22 | "lint": "yarn workspaces foreach -vp run lint", 23 | "lint:style:fix": "yarn workspaces foreach -vp run lint:style:fix", 24 | "demo": "yarn run demo:local", 25 | "demo:local": "concurrently --raw --kill-others \"yarn run start:snap\" \"yarn run start:example\"" 26 | }, 27 | "devDependencies": { 28 | "@chainsafe/eslint-config": "^1.0.0", 29 | "@rushstack/eslint-patch": "^1.1.4", 30 | "@types/eslint": "^7", 31 | "@types/mocha": "^9.1.1", 32 | "@types/node": "^12.12.35", 33 | "concurrently": "^7.1.0", 34 | "eslint": "7", 35 | "mocha": "^10.0.0", 36 | "ts-node": "^10.8.0", 37 | "typescript": "4.6.4" 38 | }, 39 | "resolutions": { 40 | "web3/bignumber.js": "2.0.8", 41 | "ethereumjs-abi": "0.6.8" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.3](https://github.com/ChainSafe/filsnap/compare/filsnap-adapter-v2.1.2...filsnap-adapter-v2.1.3) (2023-03-03) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * flask1.25 ([#246](https://github.com/ChainSafe/filsnap/issues/246)) ([155e857](https://github.com/ChainSafe/filsnap/commit/155e857411545d204d95901ae25ee90534ca7fc9)) 9 | 10 | 11 | ### Dependencies 12 | 13 | * The following workspace dependencies were updated 14 | * dependencies 15 | * @chainsafe/filsnap-types bumped to 2.1.3 16 | 17 | ## [2.1.2](https://github.com/ChainSafe/filsnap/compare/filsnap-adapter-v2.1.1...filsnap-adapter-v2.1.2) (2022-11-03) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * change default origin from `ipfs` to `npm` ([ccaa97a](https://github.com/ChainSafe/filsnap/commit/ccaa97abbac21d463fef7c31d8a8067ab074d97b)) 23 | * default origin ([#219](https://github.com/ChainSafe/filsnap/issues/219)) ([ccaa97a](https://github.com/ChainSafe/filsnap/commit/ccaa97abbac21d463fef7c31d8a8067ab074d97b)) 24 | 25 | ### [2.1.1](https://github.com/ChainSafe/filsnap/compare/filsnap-adapter-v2.1.0...filsnap-adapter-v2.1.1) (2022-04-11) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * Page refresh disconnects snap ([#140](https://github.com/ChainSafe/filsnap/issues/140)) ([631690e](https://github.com/ChainSafe/filsnap/commit/631690e4b4cec8441275d035d4905d532cb65256)) 31 | -------------------------------------------------------------------------------- /packages/adapter/README.md: -------------------------------------------------------------------------------- 1 | # FilSnap adapter 2 | ![](https://github.com/chainsafe/filsnap/workflows/ci/badge.svg) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | ![](https://img.shields.io/badge/yarn-%3E%3D1.17.0-orange.svg?style=flat-square) 6 | ![Discord](https://img.shields.io/discord/608204864593461248?color=blue&label=Discord&logo=discord) 7 | 8 | FilSnap adapter is used to install Filecoin snap and expose API toward snap. 9 | 10 | For more details on Filecoin snap itself see [snap repo](https://github.com/chainsafe/filsnap) or read full [Filecoin snap documentation](https://github.com/chainsafe/filsnap/wiki). 11 | 12 | ## Usage 13 | 14 | Adapter has only exposed function for installing Filecoin snap. 15 | 16 | ```typescript 17 | async function enableFilecoinSnap( 18 | config: Partial, 19 | snapOrigin?: string 20 | ): Promise 21 | ``` 22 | 23 | On snap installation, it is possible to send full or partial configuration. 24 | If you only provide `network` property a predefined configuration for the specified network will be used. 25 | Other properties are optional but will override default values if provided. 26 | 27 | Below you can see structure of config object: 28 | 29 | ```typescript 30 | export interface SnapConfig { 31 | derivationPath: string; 32 | token: string; 33 | network: FilecoinNetwork; // "f" || "t" 34 | rpcUrl: string; 35 | unit?: UnitConfiguration; 36 | } 37 | 38 | export interface UnitConfiguration { 39 | symbol: string; 40 | decimals: number; 41 | image?: string; 42 | customViewUrl?: string; 43 | } 44 | ``` 45 | 46 | After snap installation, this function returns `MetamaskFilecoinSnap` object that can be used to retrieve snap API. 47 | An example of initializing Filecoin snap and invoking snap API is shown below. 48 | 49 | ```typescript 50 | // install snap and fetch API 51 | const snap = await enableFilecoinSnap({network: "t"}); 52 | const api = await metamaskFilecoinSnap.getFilecoinSnapApi(); 53 | 54 | // invoke API 55 | const address = await api.getAddress(); 56 | 57 | console.log(`Snap installed, account generated with address: ${address}`); 58 | ``` -------------------------------------------------------------------------------- /packages/adapter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainsafe/filsnap-adapter", 3 | "version": "2.1.3", 4 | "main": "./build/index.js", 5 | "module": "./build/index.js", 6 | "types": "./build/index.d.ts", 7 | "author": "chainsafe ", 8 | "license": "(Apache-2.0 AND MIT)", 9 | "homepage": "https://github.com/chainsafe/filecoin-metamask-snap/tree/master/packages/adapter", 10 | "keywords": [ 11 | "filecoin", 12 | "metamask", 13 | "snap", 14 | "dapp" 15 | ], 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "description": "Adapter for installing Filsnap", 20 | "scripts": { 21 | "prebuild": "rm -rf build", 22 | "build": "yarn run build:lib && yarn run build:types", 23 | "build:lib": "tsc --build tsconfig.json", 24 | "build:types": "tsc --emitDeclarationOnly", 25 | "lint": "yarn run lint:style && yarn run lint:types", 26 | "lint:types": "tsc --noEmit --pretty", 27 | "lint:style": "eslint --color 'src/**/*.{js,ts,tsx}'", 28 | "lint:style:fix": "yarn run lint:style --fix", 29 | "test": "exit 0" 30 | }, 31 | "dependencies": { 32 | "@chainsafe/filsnap-types": "workspace:^" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^12.12.35", 36 | "eslint": "^7.11.0", 37 | "typescript": "4.3.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/adapter/src/index.ts: -------------------------------------------------------------------------------- 1 | import { SnapConfig } from "@chainsafe/filsnap-types"; 2 | import { 3 | hasMetaMask, 4 | isMetamaskSnapsSupported, 5 | isSnapInstalled, 6 | } from "./utils"; 7 | import { MetamaskFilecoinSnap } from "./snap"; 8 | 9 | const defaultSnapOrigin = "npm:@chainsafe/polkadot-snap"; 10 | 11 | export { MetamaskFilecoinSnap } from "./snap"; 12 | export { 13 | hasMetaMask, 14 | isMetamaskSnapsSupported, 15 | isSnapInstalled, 16 | } from "./utils"; 17 | 18 | export type SnapInstallationParamNames = "version" | string; 19 | 20 | /** 21 | * Install and enable Filecoin snap 22 | * 23 | * Checks for existence of Metamask and version compatibility with snaps before installation. 24 | * 25 | * Provided snap configuration must define at least network property so predefined configuration can be selected. 26 | * All other properties are optional, and if present will overwrite predefined property. 27 | * 28 | * @param config - SnapConfig 29 | * @param snapOrigin 30 | * 31 | * @return MetamaskFilecoinSnap - adapter object that exposes snap API 32 | */ 33 | export async function enableFilecoinSnap( 34 | config: Partial, 35 | snapOrigin?: string, 36 | snapInstallationParams: Record = {} 37 | ): Promise { 38 | const snapId = snapOrigin ?? defaultSnapOrigin; 39 | 40 | // check all conditions 41 | if (!hasMetaMask()) { 42 | throw new Error("Metamask is not installed"); 43 | } 44 | if (!(await isMetamaskSnapsSupported())) { 45 | throw new Error("Current Metamask version doesn't support snaps"); 46 | } 47 | if (!config.network) { 48 | throw new Error("Configuration must at least define network type"); 49 | } 50 | 51 | const isInstalled = await isSnapInstalled(snapId); 52 | 53 | if (!isInstalled) { 54 | // // enable snap 55 | await window.ethereum.request({ 56 | method: "wallet_requestSnaps", 57 | params: { 58 | [snapId]: { ...snapInstallationParams }, 59 | }, 60 | }); 61 | } 62 | 63 | //await unlockMetamask(); 64 | 65 | // create snap describer 66 | const snap = new MetamaskFilecoinSnap(snapOrigin || defaultSnapOrigin); 67 | // set initial configuration 68 | await (await snap.getFilecoinSnapApi()).configure(config); 69 | // return snap object 70 | return snap; 71 | } 72 | -------------------------------------------------------------------------------- /packages/adapter/src/methods.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MessageStatus, 3 | MetamaskFilecoinRpcRequest, 4 | MessageRequest, 5 | SignedMessage, 6 | SignMessageResponse, 7 | SnapConfig, 8 | MessageGasEstimate, 9 | SignRawMessageResponse, 10 | } from "@chainsafe/filsnap-types"; 11 | import { MetamaskFilecoinSnap } from "./snap"; 12 | 13 | async function sendSnapMethod( 14 | request: MetamaskFilecoinRpcRequest, 15 | snapId: string 16 | ): Promise { 17 | return await window.ethereum.request({ 18 | method: "wallet_invokeSnap", 19 | params: { 20 | request, 21 | snapId, 22 | }, 23 | }); 24 | } 25 | 26 | export async function getAddress(this: MetamaskFilecoinSnap): Promise { 27 | return await sendSnapMethod({ method: "fil_getAddress" }, this.snapId); 28 | } 29 | 30 | export async function getPublicKey( 31 | this: MetamaskFilecoinSnap 32 | ): Promise { 33 | return await sendSnapMethod({ method: "fil_getPublicKey" }, this.snapId); 34 | } 35 | 36 | export async function getBalance(this: MetamaskFilecoinSnap): Promise { 37 | return await sendSnapMethod({ method: "fil_getBalance" }, this.snapId); 38 | } 39 | 40 | export async function exportPrivateKey( 41 | this: MetamaskFilecoinSnap 42 | ): Promise { 43 | return await sendSnapMethod({ method: "fil_exportPrivateKey" }, this.snapId); 44 | } 45 | 46 | export async function configure( 47 | this: MetamaskFilecoinSnap, 48 | configuration: SnapConfig 49 | ): Promise { 50 | return await sendSnapMethod( 51 | { method: "fil_configure", params: { configuration: configuration } }, 52 | this.snapId 53 | ); 54 | } 55 | 56 | export async function signMessage( 57 | this: MetamaskFilecoinSnap, 58 | message: MessageRequest 59 | ): Promise { 60 | return await sendSnapMethod( 61 | { method: "fil_signMessage", params: { message: message } }, 62 | this.snapId 63 | ); 64 | } 65 | 66 | export async function signMessageRaw( 67 | this: MetamaskFilecoinSnap, 68 | rawMessage: string 69 | ): Promise { 70 | return await sendSnapMethod( 71 | { method: "fil_signMessageRaw", params: { message: rawMessage } }, 72 | this.snapId 73 | ); 74 | } 75 | 76 | export async function sendMessage( 77 | this: MetamaskFilecoinSnap, 78 | signedMessage: SignedMessage 79 | ): Promise { 80 | return await sendSnapMethod( 81 | { method: "fil_sendMessage", params: { signedMessage: signedMessage } }, 82 | this.snapId 83 | ); 84 | } 85 | 86 | export async function getMessages( 87 | this: MetamaskFilecoinSnap 88 | ): Promise { 89 | return await sendSnapMethod({ method: "fil_getMessages" }, this.snapId); 90 | } 91 | 92 | export async function calculateGasForMessage( 93 | this: MetamaskFilecoinSnap, 94 | message: MessageRequest, 95 | maxFee?: string 96 | ): Promise { 97 | return await sendSnapMethod( 98 | { 99 | method: "fil_getGasForMessage", 100 | params: { maxFee: maxFee, message: message }, 101 | }, 102 | this.snapId 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /packages/adapter/src/snap.ts: -------------------------------------------------------------------------------- 1 | import { FilecoinSnapApi } from "@chainsafe/filsnap-types"; 2 | import { 3 | calculateGasForMessage, 4 | configure, 5 | exportPrivateKey, 6 | getAddress, 7 | getBalance, 8 | getMessages, 9 | getPublicKey, 10 | sendMessage, 11 | signMessage, 12 | signMessageRaw, 13 | } from "./methods"; 14 | 15 | export class MetamaskFilecoinSnap { 16 | // snap parameters 17 | protected readonly snapOrigin: string; 18 | protected readonly snapId: string; 19 | 20 | public constructor(snapOrigin: string) { 21 | this.snapOrigin = snapOrigin; 22 | this.snapId = this.snapOrigin; 23 | } 24 | 25 | // eslint-disable-next-line @typescript-eslint/require-await 26 | public getFilecoinSnapApi = async (): Promise => { 27 | return { 28 | calculateGasForMessage: calculateGasForMessage.bind(this), 29 | configure: configure.bind(this), 30 | exportPrivateKey: exportPrivateKey.bind(this), 31 | getAddress: getAddress.bind(this), 32 | getBalance: getBalance.bind(this), 33 | getMessages: getMessages.bind(this), 34 | getPublicKey: getPublicKey.bind(this), 35 | sendMessage: sendMessage.bind(this), 36 | signMessage: signMessage.bind(this), 37 | signMessageRaw: signMessageRaw.bind(this), 38 | }; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /packages/adapter/src/types.ts: -------------------------------------------------------------------------------- 1 | import { SnapRpcMethodRequest } from "@chainsafe/filsnap-types"; 2 | 3 | declare global { 4 | interface Window { 5 | ethereum: { 6 | isMetaMask: boolean; 7 | isUnlocked: Promise; 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | request: ( 10 | request: SnapRpcMethodRequest | { method: string; params?: any } 11 | ) => Promise; 12 | on: (eventName: unknown, callback: unknown) => unknown; 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/adapter/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function hasMetaMask(): boolean { 2 | if (!window.ethereum) { 3 | return false; 4 | } 5 | return window.ethereum.isMetaMask; 6 | } 7 | 8 | export type GetSnapsResponse = { 9 | [k: string]: { 10 | permissionName?: string; 11 | id?: string; 12 | version?: string; 13 | initialPermissions?: { [k: string]: unknown }; 14 | }; 15 | }; 16 | async function getWalletSnaps(): Promise { 17 | return await window.ethereum.request({ 18 | method: "wallet_getSnaps", 19 | }); 20 | } 21 | 22 | export async function isMetamaskSnapsSupported(): Promise { 23 | try { 24 | await getWalletSnaps(); 25 | return true; 26 | } catch (e) { 27 | return false; 28 | } 29 | } 30 | 31 | /** 32 | * 33 | * @returns 34 | */ 35 | export async function isSnapInstalled( 36 | snapOrigin: string, 37 | version?: string 38 | ): Promise { 39 | console.log(await getWalletSnaps()); 40 | try { 41 | return !!Object.values(await getWalletSnaps()).find( 42 | (permission) => 43 | permission.id === snapOrigin && 44 | (!version || permission.version === version) 45 | ); 46 | } catch (e) { 47 | console.log("Failed to obtain installed snaps", e); 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/adapter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "rootDir": "src", 5 | "moduleResolution": "node", 6 | "module": "commonjs", 7 | "declaration": true, 8 | "inlineSourceMap": true, 9 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 10 | 11 | "strict": true /* Enable all strict type-checking options. */, 12 | 13 | /* Strict Type-Checking Options */ 14 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 15 | // "strictNullChecks": true /* Enable strict null checks. */, 16 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 17 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 18 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 19 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 20 | 21 | /* Additional Checks */ 22 | "noUnusedLocals": true /* Report errors on unused locals. */, 23 | "noUnusedParameters": true /* Report errors on unused parameters. */, 24 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 25 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 26 | 27 | /* Debugging Options */ 28 | "traceResolution": false /* Report module resolution log messages. */, 29 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 30 | "listFiles": false /* Print names of files part of the compilation. */, 31 | "pretty": true /* Stylize errors and messages using color and context. */, 32 | 33 | /* Experimental Options */ 34 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 35 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 36 | 37 | "lib": ["es6", "dom", "es2017"], 38 | "typeRoots": ["node_modules/@types", "src/types"] 39 | }, 40 | "include": ["src/**/*.ts"], 41 | "exclude": ["node_modules/**"], 42 | "compileOnSave": false 43 | } -------------------------------------------------------------------------------- /packages/example/.env.sample: -------------------------------------------------------------------------------- 1 | REACT_APP_SNAP_ID=local:http://localhost:8081 2 | -------------------------------------------------------------------------------- /packages/example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env 26 | -------------------------------------------------------------------------------- /packages/example/README.md: -------------------------------------------------------------------------------- 1 | # MetaMask filecoin snap example 2 | 3 | - `yarn install` 4 | - serve snap 5 | - `yarn start` 6 | 7 | To deploy, ensure config is correct in `deploy.sh`, then run: 8 | 9 | - `./deploy.sh` 10 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "2.1.0", 4 | "private": true, 5 | "license": "(Apache-2.0 AND MIT)", 6 | "devDependencies": { 7 | "eslint-config-react-app": "^6.0.0", 8 | "gh-pages": "^3.1.0" 9 | }, 10 | "dependencies": { 11 | "@chainsafe/filsnap-adapter": "workspace:^", 12 | "@chainsafe/filsnap-types": "workspace:^", 13 | "@material-ui/core": "4.12.4", 14 | "@material-ui/icons": "4.11.3", 15 | "@material-ui/lab": "latest", 16 | "@material-ui/styles": "4.11.5", 17 | "@testing-library/jest-dom": "4.2.4", 18 | "@testing-library/react": "9.5.0", 19 | "@testing-library/user-event": "7.2.1", 20 | "@types/jest": "24.9.1", 21 | "@types/node": "16.11.45", 22 | "@types/react": "18.0.25", 23 | "@types/react-dom": "18.0.8", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "react-scripts": "4.0.3", 27 | "sass": "1.54.0", 28 | "to-hex": "0.0.18", 29 | "typescript": "4.7.4" 30 | }, 31 | "scripts": { 32 | "predeploy": "yarn build", 33 | "deploy": "gh-pages --dist build --remote metamask --no-history", 34 | "start": "react-scripts start", 35 | "build": "react-scripts build", 36 | "test": "exit 0", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": "react-app" 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filsnap/9efd484323837c89a5a63197f7d0d750c94d140f/packages/example/public/favicon.ico -------------------------------------------------------------------------------- /packages/example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Filsnap showcase 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filsnap/9efd484323837c89a5a63197f7d0d750c94d140f/packages/example/public/logo192.png -------------------------------------------------------------------------------- /packages/example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filsnap/9efd484323837c89a5a63197f7d0d750c94d140f/packages/example/public/logo512.png -------------------------------------------------------------------------------- /packages/example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Dashboard} from "./containers/Dashboard/Dashboard"; 3 | import {MetaMaskContextProvider} from "./context/metamask"; 4 | 5 | function App() { 6 | 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /packages/example/src/Footer.tsx: -------------------------------------------------------------------------------- 1 | import {Box, Container, Grid, Typography} from "@material-ui/core"; 2 | import GitHubIcon from '@material-ui/icons/GitHub'; 3 | import DescriptionIcon from '@material-ui/icons/Description'; 4 | import logo from "./filecoin_logo.png"; 5 | import cs_logo from "./chain_safe_logo.png"; 6 | import React from "react"; 7 | 8 | function Footer() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | Repo 16 | 17 | 18 | 19 | 20 | 21 | Docs 22 | 23 | 24 | 25 | 26 | 27 | {"Logo"}/ 28 | 29 | 30 | {"Node 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default Footer; -------------------------------------------------------------------------------- /packages/example/src/chain_safe_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainSafe/filsnap/9efd484323837c89a5a63197f7d0d750c94d140f/packages/example/src/chain_safe_logo.png -------------------------------------------------------------------------------- /packages/example/src/components/Account/Account.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Box, Button, Card, CardContent, CardHeader, Divider, Grid, Typography} from '@material-ui/core/'; 3 | import {FilecoinSnapApi} from "@chainsafe/filsnap-types"; 4 | 5 | export interface AccountProps { 6 | address: string, 7 | publicKey: string, 8 | balance: string, 9 | balanceChange: boolean, 10 | api: FilecoinSnapApi | null 11 | } 12 | 13 | export const Account = (props: AccountProps) => { 14 | 15 | const handleExport = async () => { 16 | if (props.api) { 17 | const privateKey = await props.api.exportPrivateKey(); 18 | alert(`Your private key: ${privateKey}`); 19 | } 20 | }; 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | ADDRESS: 29 | {props.address} 30 | 31 | 32 | PUBLIC KEY: 33 | {props.publicKey} 34 | 35 | 36 | ACCOUNT BALANCE: 37 | {props.balanceChange 38 | ? {props.balance} 39 | : {props.balance} 40 | } 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/example/src/components/SignMessage/SignMessage.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from "react"; 2 | import {Box, Button, Card, CardContent, CardHeader, Dialog, Grid, TextField} from '@material-ui/core/'; 3 | import {DialogActions, DialogContent, DialogContentText, DialogTitle, Typography} from "@material-ui/core"; 4 | import {FilecoinSnapApi} from "@chainsafe/filsnap-types"; 5 | import toHex from "to-hex"; 6 | 7 | export interface SignMessageProps { 8 | api: FilecoinSnapApi | null 9 | } 10 | 11 | export const SignMessage = (props: SignMessageProps) => { 12 | const [textFieldValue, setTextFieldValue] = useState(""); 13 | const [modalBody, setModalBody] = useState(""); 14 | const [modalOpen, setModalOpen] = useState(false); 15 | 16 | const handleChange = (event: React.ChangeEvent) => { 17 | setTextFieldValue(event.target.value); 18 | }; 19 | 20 | const onSubmit = async () => { 21 | if(textFieldValue && props.api) { 22 | const rawMessage = toHex(textFieldValue, {addPrefix: true}); 23 | const sigResponse = await props.api.signMessageRaw(rawMessage); 24 | if(sigResponse.confirmed && sigResponse.error == null) { 25 | setModalBody(sigResponse.signature); 26 | setModalOpen(true); 27 | } 28 | setTextFieldValue(""); 29 | } 30 | }; 31 | 32 | return ( 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | setModalOpen(false)} 55 | aria-labelledby="alert-dialog-title" 56 | aria-describedby="alert-dialog-description" 57 | > 58 | {"Message signature"} 59 | 60 | 61 | This is signature of your message:
62 | {modalBody} 63 |
64 |
65 | 66 | 69 | 70 |
71 |
72 | ); 73 | } -------------------------------------------------------------------------------- /packages/example/src/components/TransactionTable/TransactionTable.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Paper, Table, TableContainer, TableCell, 3 | TableRow, TableHead, TableBody} from '@material-ui/core/'; 4 | import {MessageStatus} from "@chainsafe/filsnap-types"; 5 | 6 | export interface TransactionTableProps { 7 | txs: MessageStatus[]; 8 | } 9 | 10 | export const TransactionTable = (props: TransactionTableProps) => { 11 | return ( 12 | 13 | 15 | 16 | 17 | Message id 18 | Sender 19 | Destination 20 | Amount 21 | Gas Limit 22 | Gas Premium 23 | Gas Fee Cap 24 | 25 | 26 | 27 | {props.txs.map(tx => ( 28 | 29 | 30 | {tx.cid} 31 | 32 | 33 | {tx.message.from} 34 | 35 | {tx.message.to} 36 | {tx.message.value} 37 | {tx.message.gaslimit} 38 | {tx.message.gaspremium} 39 | {tx.message.gasfeecap} 40 | 41 | ))} 42 | 43 |
44 |
45 | ); 46 | }; -------------------------------------------------------------------------------- /packages/example/src/components/Transfer/Transfer.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from "react"; 2 | import { 3 | Box, 4 | Button, 5 | Card, 6 | CardContent, 7 | CardHeader, 8 | Grid, 9 | InputAdornment, 10 | Snackbar, 11 | TextField 12 | } from '@material-ui/core/'; 13 | import {Alert} from "@material-ui/lab"; 14 | import {FilecoinSnapApi} from "@chainsafe/filsnap-types"; 15 | import { attoFilToFil, filToAttoFil } from "../../services/utils"; 16 | 17 | interface ITransferProps { 18 | network: string, 19 | api: FilecoinSnapApi | null, 20 | onNewMessageCallback: any 21 | } 22 | 23 | type AlertSeverity = "success" | "warning" | "info" | "error"; 24 | 25 | export const Transfer: React.FC = ({network, api, onNewMessageCallback}) => { 26 | const [recipient, setRecipient] = useState(""); 27 | const [amount, setAmount] = useState(""); 28 | const [gasLimit, setGasLimit] = useState("0"); 29 | const [gasPremium, setGasPremium] = useState("0"); 30 | const [gasFeeCap, setGasFeeCap] = useState("0"); 31 | const [maxFee, setMaxFee] = useState("0"); 32 | 33 | const [alert, setAlert] = useState(false); 34 | const [severity, setSeverity] = useState("success" as AlertSeverity); 35 | const [message, setMessage] = useState(""); 36 | 37 | const handleRecipientChange = useCallback((event: React.ChangeEvent) => { 38 | setRecipient(event.target.value); 39 | }, [setRecipient]); 40 | 41 | const handleAmountChange = useCallback((event: React.ChangeEvent) => { 42 | setAmount(event.target.value); 43 | }, [setAmount]); 44 | 45 | const handleGasLimitChange = useCallback((event: React.ChangeEvent) => { 46 | setGasLimit(event.target.value); 47 | }, [setGasLimit]); 48 | 49 | const handleGasPremiumChange = useCallback((event: React.ChangeEvent) => { 50 | setGasPremium(event.target.value); 51 | }, [setGasPremium]); 52 | 53 | const handleMaxFeeChange = useCallback((event: React.ChangeEvent) => { 54 | setMaxFee(event.target.value); 55 | }, [setMaxFee]); 56 | 57 | const handleGasFeeCapChange = useCallback((event: React.ChangeEvent) => { 58 | setGasFeeCap(event.target.value); 59 | }, [setGasFeeCap]); 60 | 61 | const showAlert = (severity: AlertSeverity, message: string) => { 62 | setSeverity(severity); 63 | setMessage(message); 64 | setAlert(true); 65 | }; 66 | 67 | const onAutoFillGas = useCallback(async () => { 68 | if (recipient && amount && api) { 69 | 70 | const messageEstimate = (maxFee === "0") ? await api.calculateGasForMessage({ 71 | to: recipient, 72 | value: BigInt(filToAttoFil(amount)).toString() 73 | }) : await api.calculateGasForMessage({ 74 | to: recipient, 75 | value: BigInt(filToAttoFil(amount)).toString() 76 | }, maxFee); 77 | setGasPremium(attoFilToFil(messageEstimate.gaspremium)); 78 | setGasFeeCap(attoFilToFil(messageEstimate.gasfeecap)); 79 | setGasLimit(attoFilToFil(messageEstimate.gaslimit.toString())); 80 | setMaxFee(attoFilToFil(messageEstimate.maxfee)); 81 | } else { 82 | showAlert("error", "Please first fill in Recipient and Amount fields"); 83 | } 84 | }, [recipient, amount, maxFee, api]); 85 | 86 | const onSubmit = useCallback(async () => { 87 | if (amount && recipient && api) { 88 | // Temporary signature method until sending is implemented 89 | const signedMessageResponse = await api.signMessage({ 90 | to: recipient, 91 | value: BigInt(filToAttoFil(amount)).toString(), 92 | gaslimit: Number(filToAttoFil(gasLimit)), 93 | gasfeecap: filToAttoFil(gasFeeCap), 94 | gaspremium: filToAttoFil(gasPremium) 95 | }); 96 | if(signedMessageResponse.error != null) { 97 | showAlert("error", "Error on signing message"); 98 | } else if(signedMessageResponse.error == null && !signedMessageResponse.confirmed) { 99 | showAlert("info", "Signing message declined"); 100 | } else { 101 | showAlert("info", `Message signature: ${signedMessageResponse.signedMessage.signature.data}`); 102 | const txResult = await api.sendMessage(signedMessageResponse.signedMessage); 103 | showAlert("info", `Message sent with cid: ${txResult.cid}`); 104 | } 105 | 106 | // clear form 107 | setAmount(""); 108 | setRecipient(""); 109 | setGasFeeCap("0"); 110 | setGasPremium("0"); 111 | setGasLimit("0"); 112 | setMaxFee("0"); 113 | // inform to refresh messages display 114 | onNewMessageCallback(); 115 | } 116 | }, [amount, recipient, api, gasLimit, gasFeeCap, gasPremium, onNewMessageCallback]); 117 | 118 | return ( 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | FIL}} 130 | onChange={handleAmountChange} size="medium" fullWidth id="amount" label="Amount" variant="outlined" value={amount}> 131 | 132 | 133 | FIL}} 135 | onChange={handleGasLimitChange} size="medium" fullWidth id="gaslimit" label="Gas Limit" variant="outlined" value={gasLimit}> 136 | 137 | 138 | FIL}} 140 | onChange={handleGasPremiumChange} size="medium" fullWidth id="gaspremium" label="Gas Premium" variant="outlined" value={gasPremium}> 141 | 142 | 143 | FIL}} 145 | onChange={handleGasFeeCapChange} size="medium" fullWidth id="gasfeecap" label="Gas Fee Cap" variant="outlined" value={gasFeeCap}> 146 | 147 | 148 | FIL}} 150 | onChange={handleMaxFeeChange} size="medium" fullWidth id="maxfee" label="Max fee (0.1 FIL if not set)" variant="outlined" value={maxFee}> 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | setAlert(false)} 163 | anchorOrigin={{ 164 | vertical: 'bottom', 165 | horizontal: 'left', 166 | }}> 167 | setAlert(false)}> 168 | {`${message} `} 169 | 170 | 171 | 172 | 173 | ); 174 | }; 175 | -------------------------------------------------------------------------------- /packages/example/src/containers/Dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useContext, useEffect, useState } from "react"; 2 | import { 3 | Box, Card, CardContent, CardHeader, 4 | Container, Grid, InputLabel, MenuItem, Select, Typography, 5 | } from '@material-ui/core/'; 6 | import { MetaMaskConnector } from "../MetaMaskConnector/MetaMaskConnector"; 7 | import { MetaMaskContext } from "../../context/metamask"; 8 | import { Account } from "../../components/Account/Account"; 9 | import { FilecoinSnapApi, MessageStatus } from "@chainsafe/filsnap-types"; 10 | import { TransactionTable } from "../../components/TransactionTable/TransactionTable"; 11 | import { SignMessage } from "../../components/SignMessage/SignMessage"; 12 | import { Transfer } from "../../components/Transfer/Transfer"; 13 | import Footer from "../../Footer"; 14 | 15 | export const Dashboard = () => { 16 | 17 | const [state] = useContext(MetaMaskContext); 18 | const [balance, setBalance] = useState(""); 19 | const [address, setAddress] = useState(""); 20 | const [publicKey, setPublicKey] = useState(""); 21 | const [messages, setMessages] = useState([]); 22 | const [networks, setNetworks] = useState<"t" | "f">("f") 23 | 24 | const [balanceChange, setBalanceChange] = useState(false); 25 | 26 | const [network, setNetwork] = useState<"f" | "t">("f"); 27 | 28 | const [api, setApi] = useState(null); 29 | 30 | const handleNetworkChange = async (event: React.ChangeEvent<{ value: any }>) => { 31 | const selectedNetwork = event.target.value as "f" | "t"; 32 | if (selectedNetwork === network) return; 33 | if (api) { 34 | try { 35 | await api.configure({ network: selectedNetwork }); 36 | setNetworks(selectedNetwork) 37 | setNetwork(selectedNetwork); 38 | setMessages(await api.getMessages()); 39 | } catch (e) { 40 | console.error("Unable to change network", e) 41 | } 42 | } 43 | }; 44 | 45 | const handleNewMessage = useCallback(async () => { 46 | if (api) { 47 | setMessages(await api.getMessages()); 48 | } 49 | }, [api, setMessages]); 50 | 51 | useEffect(() => { 52 | (async () => { 53 | if (state.filecoinSnap.isInstalled && state.filecoinSnap.snap) { 54 | const filecoinApi = await state.filecoinSnap.snap.getFilecoinSnapApi(); 55 | setApi(filecoinApi); 56 | } 57 | })(); 58 | }, [state.filecoinSnap.isInstalled, state.filecoinSnap.snap]); 59 | 60 | useEffect(() => { 61 | (async () => { 62 | if (api) { 63 | setAddress(await api.getAddress()); 64 | setPublicKey(await api.getPublicKey()); 65 | setBalance(await api.getBalance()); 66 | setMessages(await api.getMessages()); 67 | } 68 | })(); 69 | }, [api, network]); 70 | 71 | useEffect(() => { 72 | // periodically check balance 73 | const interval = setInterval(async () => { 74 | if (api) { 75 | const newBalance = await api.getBalance(); 76 | if (newBalance !== balance) { 77 | setBalanceChange(true); 78 | setBalance(newBalance); 79 | } else { 80 | setBalanceChange(false) 81 | } 82 | } 83 | }, 30000); // every 30 seconds ~ 1 epoch 84 | return () => clearInterval(interval); 85 | }, [api, balance, setBalance, setBalanceChange]); 86 | 87 | return ( 88 | 89 | 90 | 91 | 92 | Filsnap demo 93 | 94 | 95 | Filsnap enables Filecoin network inside Metamask. 96 | 97 | 98 | {!state.filecoinSnap.isInstalled && <> 99 | 100 |