From b874af9654796e52fa12a743cfce6851b3adc969 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Jul 2021 13:58:09 +0100 Subject: [PATCH] deprecate v1 and add V2 job details, add migration details Add two migration examples Update specs Add better docs for External Initiators Co-authored-by: Alex Roan Co-authored-by: connorwstein Co-authored-by: Stephen Fluin --- _data/navigation.js | 65 +++- _includes/main.liquid | 1 + _src/docs.css | 4 +- _src/images/dag_example.png | Bin 0 -> 16184 bytes _src/prism-jpv2.js | 28 ++ _src/prism.css | 265 ++++++++-------- _src/prism.js | 28 +- docs/External Adapters/external-adapters.md | 2 +- .../building-external-initiators.md | 26 +- .../external-initiators-in-nodes.md | 76 ++++- .../external-initiators-introduction.md | 15 +- .../Node Operators/configuration-variables.md | 9 +- docs/Oracle Jobs/Job Types/cron.md | 42 +++ docs/Oracle Jobs/Job Types/direct_request.md | 98 ++++++ docs/Oracle Jobs/Job Types/flux_monitor.md | 78 +++++ docs/Oracle Jobs/Job Types/keeper.md | 31 ++ .../Job Types/offchain_reporting.md | 110 +++++++ docs/Oracle Jobs/Job Types/webhook.md | 45 +++ docs/Oracle Jobs/Task Types/pipelines.md | 33 ++ docs/Oracle Jobs/Task Types/task_any.md | 36 +++ docs/Oracle Jobs/Task Types/task_bridge.md | 31 ++ docs/Oracle Jobs/Task Types/task_cborparse.md | 38 +++ docs/Oracle Jobs/Task Types/task_divide.md | 42 +++ .../Task Types/task_eth_abi_decode.md | 44 +++ .../Task Types/task_eth_abi_decode_log.md | 45 +++ .../Task Types/task_eth_abi_encode.md | 31 ++ docs/Oracle Jobs/Task Types/task_eth_call.md | 36 +++ docs/Oracle Jobs/Task Types/task_eth_tx.md | 35 ++ docs/Oracle Jobs/Task Types/task_http.md | 33 ++ docs/Oracle Jobs/Task Types/task_jsonparse.md | 39 +++ docs/Oracle Jobs/Task Types/task_mean.md | 29 ++ docs/Oracle Jobs/Task Types/task_median.md | 27 ++ docs/Oracle Jobs/Task Types/task_mode.md | 41 +++ docs/Oracle Jobs/Task Types/task_multiply.md | 34 ++ docs/Oracle Jobs/Task Types/task_sum.md | 27 ++ docs/Oracle Jobs/Task Types/tasks.md | 34 ++ docs/Oracle Jobs/adapters.md | 106 +------ docs/Oracle Jobs/initiators.md | 21 +- docs/Oracle Jobs/job-specifications.md | 10 +- docs/Oracle Jobs/jobs.md | 61 ++++ docs/Oracle Jobs/migration-v1-v2.md | 298 ++++++++++++++++++ .../vrf-security-considerations.md | 2 +- 42 files changed, 1786 insertions(+), 270 deletions(-) create mode 100644 _src/images/dag_example.png create mode 100644 _src/prism-jpv2.js create mode 100644 docs/Oracle Jobs/Job Types/cron.md create mode 100644 docs/Oracle Jobs/Job Types/direct_request.md create mode 100644 docs/Oracle Jobs/Job Types/flux_monitor.md create mode 100644 docs/Oracle Jobs/Job Types/keeper.md create mode 100644 docs/Oracle Jobs/Job Types/offchain_reporting.md create mode 100644 docs/Oracle Jobs/Job Types/webhook.md create mode 100644 docs/Oracle Jobs/Task Types/pipelines.md create mode 100644 docs/Oracle Jobs/Task Types/task_any.md create mode 100644 docs/Oracle Jobs/Task Types/task_bridge.md create mode 100644 docs/Oracle Jobs/Task Types/task_cborparse.md create mode 100644 docs/Oracle Jobs/Task Types/task_divide.md create mode 100644 docs/Oracle Jobs/Task Types/task_eth_abi_decode.md create mode 100644 docs/Oracle Jobs/Task Types/task_eth_abi_decode_log.md create mode 100644 docs/Oracle Jobs/Task Types/task_eth_abi_encode.md create mode 100644 docs/Oracle Jobs/Task Types/task_eth_call.md create mode 100644 docs/Oracle Jobs/Task Types/task_eth_tx.md create mode 100644 docs/Oracle Jobs/Task Types/task_http.md create mode 100644 docs/Oracle Jobs/Task Types/task_jsonparse.md create mode 100644 docs/Oracle Jobs/Task Types/task_mean.md create mode 100644 docs/Oracle Jobs/Task Types/task_median.md create mode 100644 docs/Oracle Jobs/Task Types/task_mode.md create mode 100644 docs/Oracle Jobs/Task Types/task_multiply.md create mode 100644 docs/Oracle Jobs/Task Types/task_sum.md create mode 100644 docs/Oracle Jobs/Task Types/tasks.md create mode 100644 docs/Oracle Jobs/jobs.md create mode 100644 docs/Oracle Jobs/migration-v1-v2.md diff --git a/_data/navigation.js b/_data/navigation.js index bfbf865d421..019db420c3c 100644 --- a/_data/navigation.js +++ b/_data/navigation.js @@ -39,6 +39,69 @@ module.exports = { }, { section: 'ORACLE JOBS', + contents: [ + { + url: '/docs/jobs/migration-v1-v2', + title: 'Migrating to v2 Jobs', + }, + { + url: '/docs/jobs/', + title: 'Jobs', + children: [ + { url: '/docs/jobs/types/cron/', title: 'Cron' }, + { + url: '/docs/jobs/types/direct-request/', + title: 'Direct Request', + }, + { + url: '/docs/jobs/types/flux-monitor/', + title: 'Flux Monitor', + }, + { url: '/docs/jobs/types/keeper/', title: 'Keeper' }, + { + url: '/docs/jobs/types/offchain-reporting/', + title: 'Off-chain Reporting', + }, + { url: '/docs/jobs/types/webhook/', title: 'Webhook' }, + ], + }, + { + url: '/docs/tasks/', + title: 'Tasks', + children: [ + { url: '/docs/jobs/task-types/http/', title: 'HTTP' }, + { url: '/docs/jobs/task-types/bridge/', title: 'Bridge' }, + { url: '/docs/jobs/task-types/jsonparse/', title: 'JSON Parse' }, + { url: '/docs/jobs/task-types/cborparse/', title: 'CBOR Parse' }, + { + url: '/docs/jobs/task-types/eth-abi-decode/', + title: 'ETH ABI Decode', + }, + { + url: '/docs/jobs/task-types/eth-abi-decode-log/', + title: 'ETH ABI Decode Log', + }, + { + url: '/docs/jobs/task-types/eth-abi-encode/', + title: 'ETH ABI Encode', + }, + { url: '/docs/jobs/task-types/eth-call/', title: 'ETH Call' }, + { url: '/docs/jobs/task-types/eth-tx/', title: 'ETH Tx' }, + + { url: '/docs/jobs/task-types/multiply/', title: 'Multiply' }, + { url: '/docs/jobs/task-types/divide/', title: 'Divide' }, + + { url: '/docs/jobs/task-types/any/', title: 'Any' }, + { url: '/docs/jobs/task-types/mean/', title: 'Mean' }, + { url: '/docs/jobs/task-types/median/', title: 'Median' }, + { url: '/docs/jobs/task-types/mode/', title: 'Mode' }, + { url: '/docs/jobs/task-types/sum/', title: 'Sum' }, + ], + }, + ], + }, + { + section: 'v1 JSON JOBS [DEPRECATED]', contents: [ { url: '/docs/job-specifications/', title: 'Job Specifications' }, { url: '/docs/core-adapters/', title: 'Core Adapters' }, @@ -294,4 +357,4 @@ module.exports = { }, ], }, -} +}; diff --git a/_includes/main.liquid b/_includes/main.liquid index 5bd34cbedd8..88842505e93 100644 --- a/_includes/main.liquid +++ b/_includes/main.liquid @@ -38,6 +38,7 @@ + diff --git a/_src/docs.css b/_src/docs.css index 7a3ae306e9d..ac232887234 100644 --- a/_src/docs.css +++ b/_src/docs.css @@ -14,10 +14,12 @@ pre { } p code, ul li code { + color: #a50909; background-color: #f6f8fa; border-radius: 3px; margin: 0; padding: 0.2em 0.4em; + font-size: 0.9em; } #page { @@ -33,7 +35,6 @@ code-tabs { overflow-x: scroll; } .tab-bar { - margin-bottom: -18px; margin-top: 32px; white-space: nowrap; } @@ -142,6 +143,7 @@ a.anchor img { display: flex; } .nav-content > div { + width:800px; max-width: 800px; margin: 0px auto; padding: 0 16px 16px 16px; diff --git a/_src/images/dag_example.png b/_src/images/dag_example.png new file mode 100644 index 0000000000000000000000000000000000000000..56894f49c6ffdba30764653d499259b2f87e5b92 GIT binary patch literal 16184 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 literal 0 HcmV?d00001 diff --git a/_src/prism-jpv2.js b/_src/prism-jpv2.js new file mode 100644 index 00000000000..81048bb6d58 --- /dev/null +++ b/_src/prism-jpv2.js @@ -0,0 +1,28 @@ +// Expand with support for job spec v2 toml +Prism.languages.jpv2dot=Prism.languages.extend('dot', {}); + +var variable = { + pattern: /\$\(.*\)/, + lookbehind: !0, + inside: { + comment: { + pattern: /[\$\(\)\.]/, + }, + keyword: { + pattern: /[^\$\(\)\.]/, + }, + }, +}; + +Prism.languages.jpv2dot['attr-value'].inside.variable = variable; +Prism.languages.jpv2dot['attr-value'].inside.markup.inside.variable = variable; + +Prism.languages.jpv2=Prism.languages.extend('toml', {}); +Prism.languages.insertBefore('jpv2','string',{ + pipeline: { + pattern: /"""(?:\\[\s\S]|[^\\])*?"""/, + inside: Prism.languages.jpv2dot, + greedy: !0, + alias: 'language-jpv2dot', + }, +}); diff --git a/_src/prism.css b/_src/prism.css index f9b6557091f..5b2894c6077 100644 --- a/_src/prism.css +++ b/_src/prism.css @@ -1,5 +1,5 @@ -/* PrismJS 1.23.0 -https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+python+solidity&plugins=line-numbers+wpd+show-language+highlight-keywords+toolbar+copy-to-clipboard */ +/* PrismJS 1.24.1 +https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+dot+go+js-extras+python+shell-session+solidity+toml&plugins=line-numbers+wpd+show-language+highlight-keywords+toolbar+copy-to-clipboard */ /** * okaidia theme for JavaScript, CSS and HTML * Loosely based on Monokai textmate theme by http://www.monokai.nl/ @@ -8,61 +8,61 @@ https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clik code[class*="language-"], pre[class*="language-"] { - color: #f8f8f2; - background: none; - text-shadow: 0 1px rgba(0, 0, 0, 0.3); - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - font-size: 1em; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; + color: #f8f8f2; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; } /* Code blocks */ pre[class*="language-"] { - padding: 1em; - margin: 24px 0; - overflow: auto; - border-radius: 0.3em; + padding: 1em; + margin: .5em 0; + overflow: auto; + border-radius: 0.3em; } :not(pre) > code[class*="language-"], pre[class*="language-"] { - background: #272822; + background: #272822; } /* Inline code */ :not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; - white-space: normal; + padding: .1em; + border-radius: .3em; + white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { - color: #8292a2; + color: #8292a2; } .token.punctuation { - color: #f8f8f2; + color: #f8f8f2; } .token.namespace { - opacity: .7; + opacity: .7; } .token.property, @@ -70,12 +70,12 @@ pre[class*="language-"] { .token.constant, .token.symbol, .token.deleted { - color: #f92672; + color: #f92672; } .token.boolean, .token.number { - color: #ae81ff; + color: #ae81ff; } .token.selector, @@ -84,7 +84,7 @@ pre[class*="language-"] { .token.char, .token.builtin, .token.inserted { - color: #a6e22e; + color: #a6e22e; } .token.operator, @@ -93,151 +93,150 @@ pre[class*="language-"] { .language-css .token.string, .style .token.string, .token.variable { - color: #f8f8f2; + color: #f8f8f2; } .token.atrule, .token.attr-value, .token.function, .token.class-name { - color: #e6db74; + color: #e6db74; } .token.keyword { - color: #66d9ef; + color: #66d9ef; } .token.regex, .token.important { - color: #fd971f; + color: #fd971f; } .token.important, .token.bold { - font-weight: bold; + font-weight: bold; } .token.italic { - font-style: italic; + font-style: italic; } .token.entity { - cursor: help; + cursor: help; } pre[class*="language-"].line-numbers { - position: relative; - padding-left: 3.8em; - counter-reset: linenumber; + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; } pre[class*="language-"].line-numbers > code { - position: relative; - white-space: inherit; + position: relative; + white-space: inherit; } .line-numbers .line-numbers-rows { - position: absolute; - pointer-events: none; - top: 0; - font-size: 100%; - left: -3.8em; - width: 3em; /* works for line-numbers below 1000 lines */ - letter-spacing: -1px; - border-right: 1px solid #999; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -} - - .line-numbers-rows > span { - display: block; - counter-increment: linenumber; - } - - .line-numbers-rows > span:before { - content: counter(linenumber); - color: #999; - display: block; - padding-right: 0.8em; - text-align: right; - } + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } code[class*="language-"] a[href], pre[class*="language-"] a[href] { - cursor: help; - text-decoration: none; + cursor: help; + text-decoration: none; } code[class*="language-"] a[href]:hover, pre[class*="language-"] a[href]:hover { - cursor: help; - text-decoration: underline; + cursor: help; + text-decoration: underline; } div.code-toolbar { - position: relative; + position: relative; } div.code-toolbar > .toolbar { - position: absolute; - top: .3em; - right: .2em; - transition: opacity 0.3s ease-in-out; - opacity: 0; + position: absolute; + top: .3em; + right: .2em; + transition: opacity 0.3s ease-in-out; + opacity: 0; } div.code-toolbar:hover > .toolbar { - opacity: 1; + opacity: 1; } /* Separate line b/c rules are thrown out if selector is invalid. IE11 and old Edge versions don't support :focus-within. */ div.code-toolbar:focus-within > .toolbar { - opacity: 1; -} - -div.code-toolbar > .toolbar .toolbar-item { - display: inline-block; -} - -div.code-toolbar > .toolbar a { - cursor: pointer; -} - -div.code-toolbar > .toolbar button { - background: none; - border: 0; - color: inherit; - font: inherit; - line-height: normal; - overflow: visible; - padding: 0; - -webkit-user-select: none; /* for button */ - -moz-user-select: none; - -ms-user-select: none; -} - -div.code-toolbar > .toolbar a, -div.code-toolbar > .toolbar button, -div.code-toolbar > .toolbar span { - color: #bbb; - font-size: .8em; - padding: 0 .5em; - background: #f5f2f0; - background: rgba(224, 224, 224, 0.2); - box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); - border-radius: .5em; -} - -div.code-toolbar > .toolbar a:hover, -div.code-toolbar > .toolbar a:focus, -div.code-toolbar > .toolbar button:hover, -div.code-toolbar > .toolbar button:focus, -div.code-toolbar > .toolbar span:hover, -div.code-toolbar > .toolbar span:focus { - color: inherit; - text-decoration: none; -} - + opacity: 1; +} + +div.code-toolbar > .toolbar > .toolbar-item { + display: inline-block; +} + +div.code-toolbar > .toolbar > .toolbar-item > a { + cursor: pointer; +} + +div.code-toolbar > .toolbar > .toolbar-item > button { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + -webkit-user-select: none; /* for button */ + -moz-user-select: none; + -ms-user-select: none; +} + +div.code-toolbar > .toolbar > .toolbar-item > a, +div.code-toolbar > .toolbar > .toolbar-item > button, +div.code-toolbar > .toolbar > .toolbar-item > span { + color: #bbb; + font-size: .8em; + padding: 0 .5em; + background: #f5f2f0; + background: rgba(224, 224, 224, 0.2); + box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); + border-radius: .5em; +} + +div.code-toolbar > .toolbar > .toolbar-item > a:hover, +div.code-toolbar > .toolbar > .toolbar-item > a:focus, +div.code-toolbar > .toolbar > .toolbar-item > button:hover, +div.code-toolbar > .toolbar > .toolbar-item > button:focus, +div.code-toolbar > .toolbar > .toolbar-item > span:hover, +div.code-toolbar > .toolbar > .toolbar-item > span:focus { + color: inherit; + text-decoration: none; +} \ No newline at end of file diff --git a/_src/prism.js b/_src/prism.js index 7f1be675751..dfde9db98bf 100644 --- a/_src/prism.js +++ b/_src/prism.js @@ -1,16 +1,22 @@ -/* PrismJS 1.23.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+python+solidity&plugins=line-numbers+wpd+show-language+highlight-keywords+toolbar+copy-to-clipboard */ +/* PrismJS 1.24.1 +https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+dot+go+js-extras+python+shell-session+solidity+toml&plugins=line-numbers+wpd+show-language+highlight-keywords+toolbar+copy-to-clipboard */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=l.reach);y+=m.value.length,m=m.next){var b=m.value;if(t.length>n.length)return;if(!(b instanceof W)){var k,x=1;if(h){if(!(k=z(v,y,n,f)))break;var w=k.index,A=k.index+k[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof W)continue;for(var E=m;E!==t.tail&&(Pl.reach&&(l.reach=N);var j=m.prev;O&&(j=I(t,j,O),y+=O.length),q(t,j,x);var C=new W(o,g?M.tokenize(S,g):S,d,S);if(m=I(t,j,C),L&&I(t,m,L),1l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function q(e,n,t){for(var r=n.next,a=0;a"+a.content+""},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var t=M.util.currentScript();function r(){M.manual||M.highlightAll()}if(t&&(M.filename=t.src,t.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var a=document.readyState;"loading"===a||"interactive"===a&&t&&t.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); -Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; -!function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; -Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; -!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},a={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:a},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:a},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:a.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:a.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=a.variable[1].inside,o=0;o]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; +Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; +!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; +!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},a={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:a},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:a},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:a.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:a.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=a.variable[1].inside,o=0;o]|(?!\x3c!--)<(?:[^<>\"']|\"[^\"]*\"|'[^']*')+>|\x3c!--(?:[^-]|-(?!->))*--\x3e)*>"].join("|")+")",a={markup:{pattern:/(^<)[\s\S]+(?=>$)/,lookbehind:!0,alias:["language-markup","language-html","language-xml"],inside:e.languages.markup}};function r(e,a){return RegExp(e.replace(//g,function(){return n}),a)}e.languages.dot={comment:{pattern:/\/\/.*|\/\*[\s\S]*?\*\/|^#.*/m,greedy:!0},"graph-name":{pattern:r("(\\b(?:digraph|graph|subgraph)[ \t\r\n]+)","i"),lookbehind:!0,greedy:!0,alias:"class-name",inside:a},"attr-value":{pattern:r("(=[ \t\r\n]*)"),lookbehind:!0,greedy:!0,inside:a},"attr-name":{pattern:r("([\\[;, \t\r\n])(?=[ \t\r\n]*=)"),lookbehind:!0,greedy:!0,inside:a},keyword:/\b(?:digraph|edge|graph|node|strict|subgraph)\b/i,"compass-point":{pattern:/(:[ \t\r\n]*)(?:[ns][ew]?|[ewc_])(?![\w\x80-\uFFFF])/,lookbehind:!0,alias:"builtin"},node:{pattern:r("(^|[^-.\\w\\x80-\\uFFFF\\\\])"),lookbehind:!0,greedy:!0,inside:a},operator:/[=:]|-[->]/,punctuation:/[\[\]{};,]/},e.languages.gv=e.languages.dot}(Prism); +Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,number:/(?:\b0x[a-f\d]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/}),delete Prism.languages.go["class-name"]; +!function(a){function e(a,e){return RegExp(a.replace(//g,function(){return"(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*"}),e)}a.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+a.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),a.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+a.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),a.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:(?:Uint|Int)(?:8|16|32)|Uint8Clamped|Float(?:32|64))?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|(?:Weak)?(?:Set|Map)|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),a.languages.insertBefore("javascript","keyword",{imports:{pattern:e("(\\bimport\\b\\s*)(?:(?:\\s*,\\s*(?:\\*\\s*as\\s+|\\{[^{}]*\\}))?|\\*\\s*as\\s+|\\{[^{}]*\\})(?=\\s*\\bfrom\\b)"),lookbehind:!0,inside:a.languages.javascript},exports:{pattern:e("(\\bexport\\b\\s*)(?:\\*(?:\\s*as\\s+)?(?=\\s*\\bfrom\\b)|\\{[^{}]*\\})"),lookbehind:!0,inside:a.languages.javascript}}),a.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|for|finally|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),a.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),a.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:e("(\\.\\s*)#?"),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|location|navigator|performance|(?:local|session)Storage|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var t=["function","function-variable","method","method-variable","property-access"],r=0;r]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; +!function(s){var n=['"(?:\\\\[^]|\\$\\([^)]+\\)|\\$(?!\\()|`[^`]+`|[^"\\\\`$])*"',"'[^']*'","\\$'(?:[^'\\\\]|\\\\[^])*'","<<-?\\s*([\"']?)(\\w+)\\1\\s[^]*?[\r\n]\\2"].join("|");s.languages["shell-session"]={command:{pattern:RegExp('^(?:[^\\s@:$#%*!/\\\\]+@[^\r\n@:$#%*!/\\\\]+(?::[^\0-\\x1F$#%*?"<>:;|]+)?|[^\0-\\x1F$#%*?"<>@:;|]+)?[$#%]'+"(?:[^\\\\\r\n'\"<$]|\\\\(?:[^\r]|\r\n?)|\\$(?!')|<>)+".replace(/<>/g,function(){return n}),"m"),greedy:!0,inside:{info:{pattern:/^[^#$%]+/,alias:"punctuation",inside:{user:/^[^\s@:$#%*!/\\]+@[^\r\n@:$#%*!/\\]+/,punctuation:/:/,path:/[\s\S]+/}},bash:{pattern:/(^[$#%]\s*)\S[\s\S]*/,lookbehind:!0,alias:"language-bash",inside:s.languages.bash},"shell-symbol":{pattern:/^[$#%]/,alias:"important"}}},output:/.(?:.*(?:[\r\n]|.$))*/},s.languages["sh-session"]=s.languages.shellsession=s.languages["shell-session"]}(Prism); Prism.languages.solidity=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:contract|enum|interface|library|new|struct|using)\s+)(?!\d)[\w$]+/,lookbehind:!0},keyword:/\b(?:_|anonymous|as|assembly|assert|break|calldata|case|constant|constructor|continue|contract|default|delete|do|else|emit|enum|event|external|for|from|function|if|import|indexed|inherited|interface|internal|is|let|library|mapping|memory|modifier|new|payable|pragma|private|public|pure|require|returns?|revert|selfdestruct|solidity|storage|struct|suicide|switch|this|throw|using|var|view|while)\b/,operator:/=>|->|:=|=:|\*\*|\+\+|--|\|\||&&|<<=?|>>=?|[-+*/%^&|<>!=]=?|[~?]/}),Prism.languages.insertBefore("solidity","keyword",{builtin:/\b(?:address|bool|string|u?int(?:8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?|byte|bytes(?:[1-9]|[12]\d|3[0-2])?)\b/}),Prism.languages.insertBefore("solidity","number",{version:{pattern:/([<>]=?|\^)\d+\.\d+\.\d+\b/,lookbehind:!0,alias:"number"}}),Prism.languages.sol=Prism.languages.solidity; -!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var o="line-numbers",a=/\n(?!$)/g,e=Prism.plugins.lineNumbers={getLine:function(e,n){if("PRE"===e.tagName&&e.classList.contains(o)){var t=e.querySelector(".line-numbers-rows");if(t){var i=parseInt(e.getAttribute("data-start"),10)||1,r=i+(t.children.length-1);n");(i=document.createElement("span")).setAttribute("aria-hidden","true"),i.className="line-numbers-rows",i.innerHTML=l,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(i),u([t]),Prism.hooks.run("line-numbers",e)}}}),Prism.hooks.add("line-numbers",function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0})}function u(e){if(0!=(e=e.filter(function(e){var n=t(e)["white-space"];return"pre-wrap"===n||"pre-line"===n})).length){var n=e.map(function(e){var n=e.querySelector("code"),t=e.querySelector(".line-numbers-rows");if(n&&t){var i=e.querySelector(".line-numbers-sizer"),r=n.textContent.split(a);i||((i=document.createElement("span")).className="line-numbers-sizer",n.appendChild(i)),i.innerHTML="0",i.style.display="block";var s=i.getBoundingClientRect().height;return i.innerHTML="",{element:e,lines:r,lineHeights:[],oneLinerHeight:s,sizer:i}}}).filter(Boolean);n.forEach(function(e){var i=e.sizer,n=e.lines,r=e.lineHeights,s=e.oneLinerHeight;r[n.length-1]=void 0,n.forEach(function(e,n){if(e&&1");(i=document.createElement("span")).setAttribute("aria-hidden","true"),i.className="line-numbers-rows",i.innerHTML=l,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(i),u([t]),Prism.hooks.run("line-numbers",e)}}}),Prism.hooks.add("line-numbers",function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0})}function u(e){if(0!=(e=e.filter(function(e){var n=function(e){return e?window.getComputedStyle?getComputedStyle(e):e.currentStyle||null:null}(e)["white-space"];return"pre-wrap"===n||"pre-line"===n})).length){var n=e.map(function(e){var n=e.querySelector("code"),t=e.querySelector(".line-numbers-rows");if(n&&t){var i=e.querySelector(".line-numbers-sizer"),r=n.textContent.split(a);i||((i=document.createElement("span")).className="line-numbers-sizer",n.appendChild(i)),i.innerHTML="0",i.style.display="block";var s=i.getBoundingClientRect().height;return i.innerHTML="",{element:e,lines:r,lineHeights:[],oneLinerHeight:s,sizer:i}}}).filter(Boolean);n.forEach(function(e){var i=e.sizer,n=e.lines,r=e.lineHeights,s=e.oneLinerHeight;r[n.length-1]=void 0,n.forEach(function(e,n){if(e&&1Chainlink external initiator repo for reference. You can see some examples of existing initiators in the blockchain folder. + +An external initiator can trigger a run for any webhook job that it has been linked to. + +The URL for triggering a run is such: + +``` +curl -X POST -H "Content-Type: application/json" --data '{"myKey": "myVal"}' http://localhost:6688/v2/jobs//runs +``` + +You will need to specify two headers: + +1. "X-Chainlink-EA-AccessKey" +1. "X-Chainlink-EA-Secret" + +## JSON jobs (DEPRECATED) + +> ⚠️ NOTE +> The documentation below refers to JSON (aka [v1 jobs](/docs/job-specifications/)) and will be removed in Chainlink 1.0.0. + +We will be using the Chainlink external initiator repo for reference. You can see some examples of existing initiators in the blockchain folder. External initiators are simple web initiators that can be activated by any job instead of just one. To set one up, you need to have a service similar to an external adapter that sends an `HTTPPost` message runs API call to your chainlink node service. Here is a sample URL for a web job could look like: @@ -16,17 +35,16 @@ Where `%s` is the jobId. External initiators make the same API call, with 2 added headers: 1. "X-Chainlink-EA-AccessKey" -2. "X-Chainlink-EA-Secret" +1. "X-Chainlink-EA-Secret" These are keys generated when you register your external initiator with your node. Triggering a run through an external initiator is as simple as making this API call to your node. All jobs with this EI configured will then be kicked off in this way. A simple external initiator in psedo code could look like this: - ``` while(True): send_api_call_with_external_initiator_access_key_headers() sleep(4) ``` -And have this job run on the same machine as your node. \ No newline at end of file +And have this job run on the same machine as your node. diff --git a/docs/External Initiators/external-initiators-in-nodes.md b/docs/External Initiators/external-initiators-in-nodes.md index af57fb4d48b..92700b72a25 100644 --- a/docs/External Initiators/external-initiators-in-nodes.md +++ b/docs/External Initiators/external-initiators-in-nodes.md @@ -4,19 +4,39 @@ date: Last Modified title: "Adding External Initiators to Nodes" permalink: "docs/external-initiators-in-nodes/" --- -1. Setup a new project in a different folder than the one your node runs on. + +## Creating an external initiator + +To create an external initiator you must use the remote API. You can do this yourself, like so: + +``` +POST http:///v2/external_initiators -d +``` + +where payload is a JSON blob that contains: + +``` +{ + "name": , + "url": +} +``` + +If a URL is provided, Chainlink will notify this URL of added and deleted jobs that can be triggered by this external initiator. This allows the external initiator to program in certain actions e.g. subscribing/unsubscribing to logs based on the job, etc. + +On creation: ``` -mkdir external-initiator -cd external-initiator +POST -d {"jobId": , "type": , "params": } ``` -Add your code to this folder. -2. Setup a *new* postgres database +On deletion: -This should not be the same as your Chainlink base. +``` +DELETE / +``` -3. Create External Initiator keys +You can use the chainlink client for convenience to access this API. Enter the [Chainlink nodes CLI](/docs/miscellaneous/#execute-commands-running-docker) and run the following command ``` @@ -26,14 +46,17 @@ chainlink initiators create `NAME`: The name you want to use for your external initiator. `URL`: The URL of your jobs endpoint. ie: `http://172.17.0.1:8080/jobs` -This will give you the environment variables you need to run your node. Copy the output. It will look something like this: +This will give you the environment variables you need to run your external initiator. Copy the output. It will look something like this: ``` ║ ei_name ║ http://localhost:8080/jobs ║ a4846e85727e46b48889c6e28b555696 ║ dnNfNhiiCTm1o6l+hGJVfCtRSSuDfZbj1VO4BkZG3E+b96lminE7yQHj2KALMAIk ║ iWt64+Q9benOf5JuGwJtQnbByN9rtHwSlElOVpHVTvGTP5Zb2Guwzy6w3wflwyYt ║ 56m38YkeCymYU0kr4Yg6x3e98CyAu+37y2+kMO2AL9lRMjA3hRA1ejFdG9UfFCAE ``` + +Be sure to save these values, since the secrets cannot be shown again. + You now can use `ei_name` as an initiator in your jobspec. -3. Set a new `.env` file, and add the respective values +Set a new `.env` file, and add the respective values ``` EI_DATABASEURL=postgresql://$USERNAME:$PASSWORD@$SERVER:$PORT/$DATABASE @@ -45,12 +68,41 @@ EI_CI_SECRET= ``` At the time of writing, the output should be in order. For example, in from the output above, `EI_IC_ACCESSKEY=a4846e85727e46b48889c6e28b555696` and so on. -4. Start your EI +Start your EI. Whatever code you used to run your external initiator, pass it the new headers created for the access headers, and then start your service. An easy way to do this is by having it read from the `.env` file you just created. Check out the Conflux External initiator for an example. You'll want to test that your job is running properly. Meeting the criteria of your EI and then checking to see if a sample job kicks off is the best way to test this. -To try a real-life example, feel free to follow along with the Conflux EI demo. +To try a real-life example, feel free to follow along with the Conflux EI demo. + +Additional external initiator reference -Additional external initiator reference \ No newline at end of file +> ⚠️ NOTE +> The External Initiator can only initiate [webhook jobs](/docs/jobs/types/webhook) that have been linked to it. Trying to initiate a job that is not linked will give an unauthorised error. + +## Deleting an external initiator + +To delete an external initiator you must use the remote API. You can do this yourself, like so: + +``` +DELETE http:///v2/external_initiators/ +``` + +You can alternatively use the chainlink client for convenience: +``` +chainlink initiators destroy +``` + +## Listing external initiators + +To see your installed external initiators: + +``` +GET http:///v2/external_initiators?size=100&page=1 +``` + +Or, using the chainlink client: +``` +chainlink initiators list +``` diff --git a/docs/External Initiators/external-initiators-introduction.md b/docs/External Initiators/external-initiators-introduction.md index 2becc2cc6b1..d8956059a25 100644 --- a/docs/External Initiators/external-initiators-introduction.md +++ b/docs/External Initiators/external-initiators-introduction.md @@ -5,21 +5,20 @@ title: "Introduction" permalink: "docs/external-initiators-introduction/" whatsnext: {"Building External Initiators":"/docs/building-external-initiators/", "Adding External Initiators to Nodes":"/docs/external-initiators-in-nodes/"} --- -External initiators allow jobs in a node to be initiated depending on some external condition. The ability to create and add external initiators to Chainlink nodes enables blockchain agnostic cross-chain compatibility. -Communication between External Initiators is handled by the Initiator Bridge. These bridges are defined in the `bridges` tab, and are the same bridges used to define external adapters. +External initiators allow jobs in a node to be initiated depending on some external condition. The ability to create and add external initiators to Chainlink nodes enables blockchain agnostic cross-chain compatibility. > 🚧 Note > > At this time of writing, external initiators do not show up in the bridges tab. However, they act exactly the same as if they did. - Initiator Bridges handle the authentication to and from the External Initiator and where to send the messages. When creating a Bridge two parameters are required: -1. A Name -2. A URL +Only the [webhook](/docs/jobs/types/webhook/) job type can be initiated using an External Initiator. + +The external initiator must be created before the webhook job, and must be referenced by name (whitelisted) in order for that external initiator to be allowed to trigger the given webhook job. -The Name is used in subsequent Job Specifications as an easy way to identify the Initiator that is triggering new Runs. The URL specifies where to send messages to the External Initiator. +When the External Initiator is created it generates two pairs of credentials: Outgoing and Incoming. The Outgoing Access Key and Secret are used to authenticate messages sent from the Core to the External Initiator. The Incoming Access Key and Secret are used to authenticate messages sent from the External Initiator to the Core. -When the Bridge is created it generates two pairs of credentials: Outgoing and Incoming. The Outgoing Access Key and Secret are used to authenticate messages sent from the Core to the External Initiator. The Incoming Access Key and Secret are used to authenticate messages sent from the External Initiator to the Core. +Then, once you've created the name, bridge, and have the correct access keys for the URL, you can proceed to use the external initiator as if it's a regular initiator in future job specs. -Then, once you've created the name, bridge, and have the correct access keys for the URL, you can proceed to use the external initiator as if it's a regular initiator in future job specs. \ No newline at end of file +For how to create an external initiator see [adding external initiators to nodes](/docs/external-initiators-in-nodes). diff --git a/docs/Node Operators/configuration-variables.md b/docs/Node Operators/configuration-variables.md index c49dc50b323..1e9d8b1c562 100644 --- a/docs/Node Operators/configuration-variables.md +++ b/docs/Node Operators/configuration-variables.md @@ -604,7 +604,8 @@ Enables or disables the node writing to the `$ROOT/log.jsonl` file. The number of block confirmations to wait before kicking off a job run. Setting this to a lower value improves node response time at the expense of occasionally submitting duplicate transactions in the event of chain re-orgs (duplicate transactions are harmless but cost some eth). -NOTE: The lowest value allowed here is 1, since setting to 0 would imply that logs are processed from the mempool before they are even mined into a block, which isn't possible with Chainlink's current architecture. +> ⚠️ NOTE +> The lowest value allowed here is 1, since setting to 0 would imply that logs are processed from the mempool before they are even mined into a block, which isn't possible with Chainlink's current architecture. ## ETH_NONCE_AUTO_SYNC @@ -699,7 +700,8 @@ See eth_resender.go for more details ## MINIMUM_CONTRACT_PAYMENT_LINK_JUELS -NOTE: This was formerly called MINIMUM_CONTRACT_PAYMENT, it will be removed in a future release. +> ⚠️ NOTE +> This was formerly called MINIMUM_CONTRACT_PAYMENT, it will be removed in a future release. - Default: _automatically set based on Chain ID, typically 100000000000000 (0.0001 LINK) on all chains except mainnet, where it is 1 LINK_ @@ -785,7 +787,8 @@ In deployments with very high burst rates, or on chains with large re-orgs, you # Misc notes -NOTE: Some env vars require a duration. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Some examples: +> ⚠️ NOTE +> Some env vars require a duration. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Some examples: `10ms` `1h15m` diff --git a/docs/Oracle Jobs/Job Types/cron.md b/docs/Oracle Jobs/Job Types/cron.md new file mode 100644 index 00000000000..720adef3f08 --- /dev/null +++ b/docs/Oracle Jobs/Job Types/cron.md @@ -0,0 +1,42 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Cron Jobs" +permalink: "docs/jobs/types/cron/" +--- + +Executes a job on a schedule. Does not rely on any kind of external trigger. + +**Spec format** + +```jpv2 +type = "cron" +schemaVersion = 1 +schedule = "CRON_TZ=UTC * */20 * * * *" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F01" +observationSource = """ + fetch [type=http method=GET url="https://chain.link/ETH-USD"] + parse [type=jsonparse path="data,price"] + multiply [type=multiply times=100] + + fetch -> parse -> multiply +""" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique fields** + +- `schedule`: the frequency with which the job is to be run. There are two ways to specify this: + - Traditional UNIX cron format, but with 6 fields, not 5. The extra field allows for "seconds" granularity. **Note:** you _must_ specify the `CRON_TZ=...` parameter if you use this format. + - `@` shorthand, e.g. `@every 1h`. This shorthand does not take account of the node's timezone, rather, it simply begins counting down the moment that the job is added to the node (or the node is rebooted). As such, no `CRON_TZ` parameter is needed. + +For all supported schedules, please refer to the [cron library documentation](https://pkg.go.dev/github.com/robfig/cron?utm_source=godoc). + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. diff --git a/docs/Oracle Jobs/Job Types/direct_request.md b/docs/Oracle Jobs/Job Types/direct_request.md new file mode 100644 index 00000000000..c7a0645bc6d --- /dev/null +++ b/docs/Oracle Jobs/Job Types/direct_request.md @@ -0,0 +1,98 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Direct Request Jobs" +permalink: "docs/jobs/types/direct-request/" +--- + +Executes a job upon receipt of an explicit request made by a user. The request is detected via a log emitted by an Oracle or Operator contract. This is similar to the legacy ethlog/runlog style of jobs. + +**Spec format** + +```jpv2 +type = "directrequest" +schemaVersion = 1 +name = "example eth request event spec" +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F02" # optional +observationSource = """ + ds [type=http method=GET url="http://example.com"] + ds_parse [type=jsonparse path="USD"] + ds_multiply [type=multiply times=100] + + ds -> ds_parse -> ds_multiply +""" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique fields** + +- `contractAddress`: the Oracle or Operator contract to monitor for requests. + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. +- `$(jobRun.logBlockHash)`: the block hash in which the initiating log was received. +- `$(jobRun.logBlockNumber)`: the block number in which the initiating log was received. +- `$(jobRun.logTxHash)`: the transaction hash that generated the initiating log. +- `$(jobRun.logAddress)`: the address of the contract to which the initiating transaction was sent. +- `$(jobRun.logTopics)`: the log's topics (`indexed` fields). +- `$(jobRun.logData)`: the log's data (non-`indexed` fields). + +**Example** + +First, let's assume that a user makes a request to the oracle using the following contract: + +```sol +contract MyClient is ChainlinkClient { + function doRequest() public { + Chainlink.Request memory req = buildChainlinkRequest(specId, address(this), this.fulfill.selector); + req.add("fetchURL", "https://datafeed.xyz/eth"); + req.add("jsonPath", "data,result"); + } + + function fulfill(bytes32 requestID, uint256 answer) public { + // ... + } +} +``` + +A direct request job using the following pipeline could be used to fulfill this request: + +```dot +// First, we parse the request log and the CBOR payload inside of it +decode_log [type="ethabidecodelog" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)" + abi="SomeContractEvent(bytes32 requestID, bytes cborPayload)"] + +decode_cbor [type="cborparse" + data="$(decode_log.cborPayload)"] + +// Then, we use the decoded request parameters to make an HTTP fetch +fetch [type="http" url="$(decode_cbor.fetchURL)" method="get"] +parse [type="jsonparse" path="$(decode_cbor.jsonPath)" data="$(fetch)"] + +// Finally, we send a response on-chain +encode_response [type=ethabiencode + abi="(bytes32 requestId, uint256 data)" + data=<{ "requestId": $(decode_log.requestId), "data": $(parse) }>] + +encode_tx [type=ethabiencode + abi="fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data)" + data=<{ + "requestId": $(decode_log.requestId), + "payment": $(decode_log.payment), + "callbackAddress": $(decode_log.callbackAddr), + "callbackFunctionId": $(decode_log.callbackFunctionId), + "expiration": $(decode_log.cancelExpiration), + "data": $(encode_mwr) + }>] + +submit_tx [type=ethtx to="0x613a38AC1659769640aaE063C651F48E0250454C" data="$(encode_tx)"] +``` diff --git a/docs/Oracle Jobs/Job Types/flux_monitor.md b/docs/Oracle Jobs/Job Types/flux_monitor.md new file mode 100644 index 00000000000..817649b4361 --- /dev/null +++ b/docs/Oracle Jobs/Job Types/flux_monitor.md @@ -0,0 +1,78 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Flux Monitor Jobs" +permalink: "docs/jobs/types/flux-monitor/" +--- + +The Flux Monitor job type is for continually-updating data feeds that aggregate responses from multiple oracles. The oracles servicing the feed submit rounds based on several triggers: + +- An occasional poll, which must show that there has been sufficient deviation from an off-chain data source before a new result is submitted +- New rounds initiated by other oracles on the feeds. If another oracle notices sufficient deviation, all other oracles will submit their current observations as well. +- A heartbeat, which ensures that even if no deviation occurs, we submit a new result to prove liveness. This can take one of two forms: + - The "idle timer", which begins counting down each time a round is completed + - The "drumbeat", which simply ticks at a steady interval, much like a `cron` job + +**Spec format** + +```jpv2 +type = "fluxmonitor" +schemaVersion = 1 +name = "example flux monitor spec" +contractAddress = "0x3cCad4715152693fE3BC4460591e3D3Fbd071b42" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F03" + +threshold = 0.5 +absoluteThreshold = 0.0 # optional + +idleTimerPeriod = "1s" +idleTimerDisabled = false + +pollTimerPeriod = "1m" +pollTimerDisabled = false + +drumbeatEnabled = true +drumbeatSchedule = "* */20 * * * *" + +observationSource = """ + // data source 1 + ds1 [type=http method=GET url="https://pricesource1.com" + requestData=<{"coin": "ETH", "market": "USD"}>] + ds1_parse [type=jsonparse path="data,result"] + + // data source 2 + ds2 [type=http method=GET url="https://pricesource2.com" + requestData=<{"coin": "ETH", "market": "USD"}>] + ds2_parse [type=jsonparse path="data,result"] + + ds1 -> ds1_parse -> medianized_answer + ds2 -> ds2_parse -> medianized_answer + + medianized_answer [type=median] +""" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique Fields** + +- `contractAddress`: the address of the FluxAggregator contract that manages the feed. +- `threshold`: the percentage threshold of deviation from the previous on-chain answer that must be observed before a new set of observations are submitted to the contract. +- `absoluteThreshold`: the absolute numerical deviation that must be observed from the previous on-chain answer before a new set of observations are submitted to the contract. This is primarily useful with data that can legitimately sometimes hit 0, as it's impossible to calculate a percentage deviation from 0. +- `idleTimerPeriod`: the amount of time (after the successful completion of a round) after which a new round will be automatically initiated, regardless of any observed off-chain deviation. +- `idleTimerDisabled`: whether the idle timer is used to trigger new rounds. +- `drumbeatEnabled`: whether the drumbeat is used to trigger new rounds. +- `drumbeatSchedule`: the cron schedule of the drumbeat. This field supports the same syntax as the cron job type (see the [cron library documentation](https://pkg.go.dev/github.com/robfig/cron?utm_source=godoc) for details). +- `pollTimerPeriod`: the frequency with which the off-chain data source is checked for deviation against the previously submitted on-chain answer. +- `pollTimerDisabled`: whether the occasional deviation check is used to trigger new rounds. +- **Notes:** + - For duration parameters, the maximum unit of time is `h` (hour). Durations of a day or longer must be expressed in hours. + - If no time unit is provided, the default unit is nanoseconds, which is almost never what you want. + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. diff --git a/docs/Oracle Jobs/Job Types/keeper.md b/docs/Oracle Jobs/Job Types/keeper.md new file mode 100644 index 00000000000..63563b8b82e --- /dev/null +++ b/docs/Oracle Jobs/Job Types/keeper.md @@ -0,0 +1,31 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Keeper Jobs" +permalink: "docs/jobs/types/keeper/" +--- + +Keeper jobs occasionally poll a smart contract method that expresses whether something in the contract is ready for some on-chain action to be performed. When it's ready, the job executes that on-chain action. Examples: + +- Liquidations +- Rebalancing portfolios +- Rebase token supply adjustments + +**Spec format** + +```jpv2 +type = "keeper" +schemaVersion = 1 +name = "example keeper spec" +contractAddress = "0x9E40733cC9df84636505f4e6Db28DCa0dC5D1bba" +fromAddress = "0xa8037A20989AFcBC51798de9762b351D63ff462e" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F04" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique fields** + +- `contractAddress`: the address of the contract to poll and update. +- `fromAddress`: the address from which to send updates. diff --git a/docs/Oracle Jobs/Job Types/offchain_reporting.md b/docs/Oracle Jobs/Job Types/offchain_reporting.md new file mode 100644 index 00000000000..a42894e8209 --- /dev/null +++ b/docs/Oracle Jobs/Job Types/offchain_reporting.md @@ -0,0 +1,110 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Offchain Reporting Jobs" +permalink: "docs/jobs/types/offchain-reporting/" +--- + +OCR jobs are currently used very similarly to Flux Monitor jobs: they update price feeds with aggregated data from many Chainlink oracle nodes. However, they do this aggregation using a cryptographically-secure off-chain protocol that makes it possible for only a single node to submit all answers from all participating nodes during each round (with proofs that the other nodes' answers were legitimately provided by those nodes), which saves a significant amount of gas. + +#### Bootstrap node + +Every OCR cluster requires at least one bootstrap node as a kind of "rallying point" that enables the other nodes to find one another. Bootstrap nodes do not participate in the aggregation protocol and do not submit answers to the feed. + +**Spec format** + +```jpv2 +type = "offchainreporting" +schemaVersion = 1 +contractAddress = "0x27548a32b9aD5D64c5945EaE9Da5337bc3169D15" +p2pBootstrapPeers = [ + "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", +] +isBootstrapPeer = true +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F05" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique fields** + +- `contractAddress`: the address of the `OffchainReportingAggregator` contract. +- `p2pBootstrapPeers`: a list of libp2p dial addresses of the other bootstrap nodes helping oracle nodes find one another on the network. +- `isBootstrapPeer`: this must be set to `true`. + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. + +#### Oracle node + +Oracle nodes, on the other hand, are responsible for submitting answers. + +**Spec format** + +```jpv2 +type = "offchainreporting" +schemaVersion = 1 +name = "OCR: ETH/USD" +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F06" +p2pPeerID = "12D3KooWApUJaQB2saFjyEUfq6BmysnsSnhLnY5CF9tURYVKgoXK" +p2pBootstrapPeers = [ + "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", +] +isBootstrapPeer = false +keyBundleID = "7f993fb701b3410b1f6e8d4d93a7462754d24609b9b31a4fe64a0cb475a4d934" +monitoringEndpoint = "chain.link:4321" +transmitterAddress = "0xF67D0290337bca0847005C7ffD1BC75BA9AAE6e4" +observationTimeout = "10s" +blockchainTimeout = "20s" +contractConfigTrackerSubscribeInterval = "2m" +contractConfigTrackerPollInterval = "1m" +contractConfigConfirmations = 3 +observationSource = """ + // data source 1 + ds1 [type=bridge name=eth_usd] + ds1_parse [type=jsonparse path="one,two"] + ds1_multiply [type=multiply times=100] + + // data source 2 + ds2 [type=http method=GET url="https://chain.link/eth_usd" + requestData=<{"hi": "hello"}>] + ds2_parse [type=jsonparse path="three,four"] + ds2_multiply [type=multiply times=100] + + ds1 -> ds1_parse -> ds1_multiply -> answer + ds2 -> ds2_parse -> ds2_multiply -> answer + + answer [type=median] +""" +``` + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Unique fields** + +- `contractAddress`: the address of the `OffchainReportingAggregator` contract. +- `p2pPeerID`: the base58-encoded libp2p public key of this node. +- `p2pBootstrapPeers`: a list of libp2p dial addresses of the other bootstrap nodes helping oracle nodes find one another on the network. +- `keyBundleID`: the hash of the OCR key bundle to be used by this node (the Chainlink node keystore manages these key bundles — use the subcommands under `chainlink keys ocr` to interact with them or use the Operator UI). +- `monitoringEndpoint`: the URL of the telemetry endpoint to send OCR metrics to. +- `transmitterAddress`: the Ethereum address from which to send aggregated submissions to the OCR contract. +- `observationTimeout`: the maximum duration to wait before an off-chain request for data is considered to be failed/unfulfillable. +- `blockchainTimeout`: the maximum duration to wait before an on-chain request for data is considered to be failed/unfulfillable. +- `contractConfigTrackerSubscribeInterval`: the interval at which to retry subscribing to on-chain config changes if a subscription has not yet successfully been made. +- `contractConfigTrackerPollInterval`: the interval at which to proactively poll the on-chain config for changes. +- `contractConfigConfirmations`: the number of blocks to wait after an on-chain config change before considering it worthy of acting upon. + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. + diff --git a/docs/Oracle Jobs/Job Types/webhook.md b/docs/Oracle Jobs/Job Types/webhook.md new file mode 100644 index 00000000000..1b3bc31e217 --- /dev/null +++ b/docs/Oracle Jobs/Job Types/webhook.md @@ -0,0 +1,45 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Webhook Jobs" +permalink: "docs/jobs/types/webhook/" +--- + +Webhook jobs can be initiated by HTTP request, either by a user or external initiator. + +This is an example webhook job: + +```jpv2 +type = "webhook" +schemaVersion = 1 +externalInitiators = [ + { name = "my-external-initiator-1", spec = "{\"foo\": 42}" }, + { name = "my-external-initiator-2", spec = "{}" } +] +observationSource = """ + parse_request [type=jsonparse path="data,result" data="$(jobRun.requestBody)"] + multiply [type=multiply input="$(parse_request)" times="100"] + send_to_bridge [type=bridge name="my_bridge" requestData=<{ "result": $(multiply) }>] + + parse_request -> multiply -> send_to_bridge +""" +``` + +All webhook jobs can have runs triggered by a logged in user. + +Webhook jobs may additionally specify zero or more external initiators, which can also trigger runs for this job. The name must exactly match the name of the referred external initiator. The external initiator definition here must contain a `spec` which defines the JSON payload that will be sent to the External Initiator on job creation if the external initiator has a URL. If you don't care about the spec, you can simply use the empty JSON object. + +**Unique fields** + +- `externalInitiators`: an array of `{name, spec}` objects, where `name` is the name registered with the node, and `spec` is the job spec to be forwarded to the external initiator when it is created. + +**Shared fields** +See [shared fields](/docs/jobs/#shared-fields). + +**Job type specific pipeline variables** + +- `$(jobSpec.databaseID)`: the ID of the job spec in the local database. You shouldn't need this in 99% of cases. +- `$(jobSpec.externalJobID)`: the globally-unique job ID for this job. Used to coordinate between node operators in certain cases. +- `$(jobSpec.name)`: the local name of the job. +- `$(jobRun.meta)`: a map of metadata that can be sent to a bridge, etc. +- `$(jobRun.requestBody)`: the body of the request that initiated the job run. diff --git a/docs/Oracle Jobs/Task Types/pipelines.md b/docs/Oracle Jobs/Task Types/pipelines.md new file mode 100644 index 00000000000..daa0de8b530 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/pipelines.md @@ -0,0 +1,33 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Job Pipelines" +permalink: "docs/jobs/task-types/pipelines/" +--- + +## Writing pipelines + +Pipelines are composed of tasks arranged in a DAG (directed acyclic graph) expressed in DOT syntax: + +[DOT (graph description language) - Wikipedia](https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29#Directed_graphs) + +Each node in the graph is a task, and has both a user-specified ID as well as a set of configuration parameters/attributes: + +```dot +my_fetch_task [type="http" method="get" url="https://chain.link/eth_usd"] +``` + +The edges between tasks define how data flows from one task to the next. Some tasks can have multiple inputs (such as `median`), while others are limited to 0 (`http`) or 1 (`jsonparse`). + +```dot +data_source_1 [type="http" method="get" url="https://chain.link/eth_usd"] +data_source_2 [type="http" method="get" url="https://coingecko.com/eth_usd"] +medianize_data [type="median"] +submit_to_ea [type="bridge" name="my_bridge"] + +data_source_1 -> medianize_data +data_source_2 -> medianize_data +medianize_data -> submit_to_ea +``` + +![DAG Example](/images/dag_example.png) diff --git a/docs/Oracle Jobs/Task Types/task_any.md b/docs/Oracle Jobs/Task Types/task_any.md new file mode 100644 index 00000000000..a2c46135f99 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_any.md @@ -0,0 +1,36 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "'Any' Task" +permalink: "docs/jobs/task-types/any/" +--- + +Returns a random value from the set of inputs passed in. + +**Parameters** + +None. + +**Inputs** + +Can be anything. + +**Outputs** + +A randomly-selected value from the set of inputs. + +**Example** + +```dot +fetch1 [type="http" ...] +fetch2 [type="http" ...] +fetch3 [type="http" ...] +pick_any [type="any"] + +fetch1 -> pick_any +fetch2 -> pick_any +fetch3 -> pick_any +``` + +`pick_any` will return either the result of `fetch1`, `fetch2`, or `fetch3`. + diff --git a/docs/Oracle Jobs/Task Types/task_bridge.md b/docs/Oracle Jobs/Task Types/task_bridge.md new file mode 100644 index 00000000000..53569c69d9d --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_bridge.md @@ -0,0 +1,31 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Bridge Task" +permalink: "docs/jobs/task-types/bridge/" +--- + +Bridge tasks make HTTP POST requests to pre-configured URLs. Bridges can be configured via the UI or the CLI, and are referred to by a simple user-specified name. This is the way that most jobs interact with [External Adapters](/docs/external-adapters/). + +**Parameters** + +- `name`: an arbitrary name given to the bridge by the node operator. +- `requestData` (optional): a statically-defined payload to be sent to the external adapter. +- `async` (optional): a boolean indicating whether the task should hibernate and wait for the Bridge to make an HTTP request back to the node at a later time with the result. + +**Outputs** + +A string containing the response body. + +**Example** + +```dot +my_bridge_task [type="bridge" + name="some_bridge" + requestData=<{ + "foo": $(foo), + "bar": $(bar), + "jobID": 123 + }>] +``` + diff --git a/docs/Oracle Jobs/Task Types/task_cborparse.md b/docs/Oracle Jobs/Task Types/task_cborparse.md new file mode 100644 index 00000000000..4b52b1054fe --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_cborparse.md @@ -0,0 +1,38 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "CBOR Parse Task" +permalink: "docs/jobs/task-types/cborparse/" +--- + +CBOR Parse tasks parse a CBOR payload, typically as part of a Direct Request workflow. In Direct Request, a user makes an on-chain request using a `ChainlinkClient` contract, which encodes the request parameters as CBOR. See below for an example. + +**Parameters** + +- `data`: a byte array containing the CBOR payload. + +**Outputs** + +A map containing the request parameters. Parameters can be individually accessed using `$(dot.accessors)`. + +**Example** + +```dot +// First, we parse the request log and the CBOR payload inside of it +decode_log [type="ethabidecodelog" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)" + abi="SomeContractEvent(bytes32 requestID, bytes cborPayload)"] + +decode_cbor [type="cborparse" + data="$(decode_log.cborPayload)"] + +// Then, we use the decoded request parameters to make an HTTP fetch +fetch [type="http" url="$(decode_cbor.fetchURL)" method="get"] +parse [type="jsonparse" path="$(decode_cbor.jsonPath)" data="$(fetch)"] + +// ... etc ... +``` + +See the [Direct Request page](/docs/jobs/types/direct-request/) for a more comprehensive example. + diff --git a/docs/Oracle Jobs/Task Types/task_divide.md b/docs/Oracle Jobs/Task Types/task_divide.md new file mode 100644 index 00000000000..593b80af2f7 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_divide.md @@ -0,0 +1,42 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Divide Task" +permalink: "docs/jobs/task-types/divide/" +--- + +Divides the provided `input` and `times` values. + +**Parameters** + +- `input`: the value to be divided. Possible values: + - number + - stringified number + - bytes-ified number + - `$(variable)` +- `divisor`: the value by which to divide the `input`. + - number + - stringified number + - bytes-ified number + - `$(variable)` +- `precision`: the number of decimal places to retain. + - number + - stringified number + - bytes-ified number + - `$(variable)` + +**Outputs** + +The result of the division. + +**Example** + +```dot +my_divide_task [type="divide" + input="$(json_parse_result)" + divisor="3" + precision="2"] +``` + +Given the input `10`, the task will return `3.33`. + diff --git a/docs/Oracle Jobs/Task Types/task_eth_abi_decode.md b/docs/Oracle Jobs/Task Types/task_eth_abi_decode.md new file mode 100644 index 00000000000..a89c132a9f6 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_eth_abi_decode.md @@ -0,0 +1,44 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "ETH ABI Decode Task" +permalink: "docs/jobs/task-types/eth-abi-decode/" +--- + +Decodes a ETH ABI-encoded payload, typically the result of an [ETH Call task](/docs/jobs/task-types/eth-call/). + +**Parameters** + +- `abi`: a canonical ETH ABI argument string. Should be formatted exactly as in Solidity. Each argument must be named. Examples: + - `uint256 foo, bytes32 bar, address[] baz` + - `address a, uint80[3][] u, bytes b, bytes32 b32` +- `data`: the ABI-encoded payload to decode. Can be: + - a byte array + - a hex-encoded string beginning with `0x` + +**Outputs** + +A map containing the decoded values. + +**Example** + +```dot +decode [type="ethabidecode" + abi="bytes32 requestID, uint256 price, address[] oracles" + data="$(eth_call_result)"] +``` + +This task will return a map with the following schema: + +```js +{ + "requestID": ..., // [32]byte value + "price": ..., // a number + "oracles": [ + "0x859AAa51961284C94d970B47E82b8771942F1980", + "0x51DE85B0cD5B3684865ECfEedfBAF12777cd0Ff8", + ... + ] +} +``` + diff --git a/docs/Oracle Jobs/Task Types/task_eth_abi_decode_log.md b/docs/Oracle Jobs/Task Types/task_eth_abi_decode_log.md new file mode 100644 index 00000000000..6b562d031ac --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_eth_abi_decode_log.md @@ -0,0 +1,45 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "ETH ABI Decode Log Task" +permalink: "docs/jobs/task-types/eth-abi-decode-log/" +--- + +Decodes a log emitted by an ETH contract. + +**Parameters** + +- `abi`: a canonical ETH log event definition. Should be formatted exactly as in Solidity. Each argument must be named. Examples: + - `NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt)` + - `AuthorizedSendersChanged(address[] senders)` +- `data`: the ABI-encoded log data. Can be: + - a byte array + - a hex-encoded string beginning with `0x` + - ... but generally should just be set to `$(jobRun.logData)` (see the [Direct Request page](/docs/jobs/types/direct-request/)) +- `topics`: the ABI-encoded log topics (i.e., the `indexed` parameters) + - an array of bytes32 values + - an array of hex-encoded bytes32 values beginning with `0x` + - ... but generally should just be set to `$(jobRun.logTopics)` (see the [Direct Request page](/docs/jobs/types/direct-request/)) + +**Outputs** + +A map containing the decoded values. + +**Example** + +```dot +decode [type="ethabidecodelog" + abi="NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt)" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)"] +``` + +This task will return a map with the following schema: + +```js +{ + "roundId": ..., // a number + "startedBy": ..., // an address + "startedAt": ..., // a number +} +``` diff --git a/docs/Oracle Jobs/Task Types/task_eth_abi_encode.md b/docs/Oracle Jobs/Task Types/task_eth_abi_encode.md new file mode 100644 index 00000000000..b12f284a6d8 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_eth_abi_encode.md @@ -0,0 +1,31 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "ETH ABI Encode Task" +permalink: "docs/jobs/task-types/eth-abi-encode/" +--- + +Encodes a bytes payload according to ETH ABI encoding, typically in order to perform an [ETH Call](/docs/jobs/task-types/eth-call/) or an [ETH Tx](/docs/jobs/task-types/eth-tx/). + +**Parameters** + +- `abi`: a canonical ETH ABI argument string. Should be formatted exactly as in Solidity. Each argument must be named. If a method name is provided, the 4-byte method signature is prepended to the result. Examples: + - `uint256 foo, bytes32 bar, address[] baz` + - `fulfillRequest(bytes32 requestID, uint256 answer)` +- `data`: a map of the values to be encoded. The task will make a best effort at converting values to the appropriate types. + +**Outputs** + +A byte array. + +**Example** + +```dot +encode [type="ethabiencode" + abi="fulfillRequest(bytes32 requestID, uint256 answer)" + data=<{ + "requestID": $(foo), + "answer": $(bar) + }>] +``` + diff --git a/docs/Oracle Jobs/Task Types/task_eth_call.md b/docs/Oracle Jobs/Task Types/task_eth_call.md new file mode 100644 index 00000000000..cbb0ca76949 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_eth_call.md @@ -0,0 +1,36 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "ETH Call Task" +permalink: "docs/jobs/task-types/eth-call/" +--- + +Makes a non-mutating contract call to the specified contract with the specified data payload. + +**Parameters** + +- `contract`: the address of the contract to call. +- `data`: the data to attach to the call (including the function selector). + +**Outputs** + +An ABI-encoded byte array containing the return value of the contract function. + +**Example** + +```dot +encode_call [type="ethabiencode" + abi="checkUpkeep(bytes data)" + data=<{ "data": $(upkeep_data) }>] + +call [type="ethcall" + contract="0xa36085F69e2889c224210F603D836748e7dC0088" + data="$(encode_call)"] + +decode_result [type="ethabidecode" + abi="bool upkeepNeeded, bytes performData" + data="$(call)"] + +encode_call -> call -> decode_result +``` + diff --git a/docs/Oracle Jobs/Task Types/task_eth_tx.md b/docs/Oracle Jobs/Task Types/task_eth_tx.md new file mode 100644 index 00000000000..9cbf6bb0a81 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_eth_tx.md @@ -0,0 +1,35 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "ETH Tx Task" +permalink: "docs/jobs/task-types/eth-tx/" +--- + +Makes a mutating transaction to the specified contract with the specified data payload. The transaction is guaranteed to succeed eventually. + +**Parameters** + +- `from`: the address of the externally-owned from which to send the transaction. +- `to`: the address of the contract to make a transaction to. +- `data`: the data to attach to the call (including the function selector). Most likely, this will be the output of an `ethabiencode` task. +- `gasLimit`: the amount of gas to attach to the transaction. +- `txMeta`: a map of metadata that is saved into the database for debugging. + +**Outputs** + +The hash of the transaction attempt that eventually succeeds (after potentially going through a gas bumping process to ensure confirmation). + +**Example** + +```dot +encode_tx [type="ethabiencode" + abi="performUpkeep(bytes performData)" + data=<{ "data": $(upkeep_data) }>] + +submit_tx [type="ethtx" + contract="0xa36085F69e2889c224210F603D836748e7dC0088" + data="$(encode_tx)"] + +encode_tx -> submit_tx +``` + diff --git a/docs/Oracle Jobs/Task Types/task_http.md b/docs/Oracle Jobs/Task Types/task_http.md new file mode 100644 index 00000000000..c9fc0a9387a --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_http.md @@ -0,0 +1,33 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "HTTP Task" +permalink: "docs/jobs/task-types/http/" +--- + +HTTP tasks make HTTP requests to arbitrary URLs. + +**Parameters** + +- `method`: the HTTP method that the request should use. +- `url`: the URL to make the HTTP request to. +- `requestData` (optional): a statically-defined payload to be sent to the external adapter. +- `allowUnrestrictedNetworkAccess` (optional): permits the task to access a URL at `localhost`, which could present a security risk. Note that Bridge tasks allow this by default. + +**Outputs** + +A string containing the response body. + +**Example** + +```dot +my_http_task [type="http" + method="put" + url="http://chain.link" + requestData=<{ + "foo": $(foo), + "bar": $(bar), + "jobID": 123 + }> + allowUnrestrictedNetworkAccess=true] +``` diff --git a/docs/Oracle Jobs/Task Types/task_jsonparse.md b/docs/Oracle Jobs/Task Types/task_jsonparse.md new file mode 100644 index 00000000000..3ac866373b4 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_jsonparse.md @@ -0,0 +1,39 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "JSON Parse Task" +permalink: "docs/jobs/task-types/jsonparse/" +--- + +JSON Parse tasks parse a JSON payload and extract a value at a given keypath. + +**Parameters** + +- `data`: the JSON string. Can be: + - string + - byte array +- `path`: the keypath to extract. Must be a comma-delimited list of keys. +- `lax` (optional): if false (or omitted), and the keypath doesn't exist, the task will error. If true, the task will return `nil` to the next task. + +**Outputs** + +The value at the provided keypath. + +**Example** + +```dot +my_json_task [type="jsonparse" + data="$(http_fetch_result)" + path="data,0,price"] +``` + +Given the a `data` value of +``` +{ + "data": [ + {"price": 123.45}, + {"price": 678.90}, + ] +} +``` +...the above task will return `123.45` (float64). diff --git a/docs/Oracle Jobs/Task Types/task_mean.md b/docs/Oracle Jobs/Task Types/task_mean.md new file mode 100644 index 00000000000..4858cf118bd --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_mean.md @@ -0,0 +1,29 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Mean Task" +permalink: "docs/jobs/task-types/mean/" +--- + +Accepts multiple numerical inputs and returns the mean (average) of them. + +**Parameters** + +- `values`: an array of values to be averaged. +- `allowedFaults` (optional): the maximum number of input tasks that can error without the Mean task erroring. If not specified, this value defaults to `N - 1`, where `N` is the number of inputs. +- `precision`: the number of decimal places in the result. + +**Outputs** + +The average of the values in the `values` array. + +**Example** + +```dot +my_mean_task [type="mean" + values=<[ $(fetch1), $(fetch2), $(fetch3) ]> + precision=2 + allowedFaults=1] +``` + +Given the inputs `2`, `5`, and `20`, the task will return `9`. diff --git a/docs/Oracle Jobs/Task Types/task_median.md b/docs/Oracle Jobs/Task Types/task_median.md new file mode 100644 index 00000000000..3fa0488f68e --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_median.md @@ -0,0 +1,27 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Median Task" +permalink: "docs/jobs/task-types/median/" +--- + +Accepts multiple numerical inputs and returns the median of them. + +**Parameters** + +- `values`: an array of values from which to select a median. +- `allowedFaults` (optional): the maximum number of input tasks that can error without the Median task erroring. If not specified, this value defaults to `N - 1`, where `N` is the number of inputs. + +**Outputs** + +The median of the values in the `values` array. + +**Example** + +```dot +my_median_task [type="median" + values=<[ $(fetch1), $(fetch2), $(fetch3) ]> + allowedFaults=1] +``` + +Given the inputs `2`, `5`, and `20`, the task will return `5`. diff --git a/docs/Oracle Jobs/Task Types/task_mode.md b/docs/Oracle Jobs/Task Types/task_mode.md new file mode 100644 index 00000000000..556623c116c --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_mode.md @@ -0,0 +1,41 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Mode Task" +permalink: "docs/jobs/task-types/mode/" +--- + +Accepts multiple numerical inputs and returns the mode (most common) of them. If more than one value occur the maximum number of times, it returns all of them. + +**Parameters** + +- `values`: an array of values from which to select a mode. +- `allowedFaults` (optional): the maximum number of input tasks that can error without the Mode task erroring. If not specified, this value defaults to `N - 1`, where `N` is the number of inputs. + +**Outputs** + +A map containing two keys: + +```js +{ + "results": [ ... ], // An array containing all of the values that occurred the maximum number of times + "occurrences": ..., // The number of times those values occurred +} +``` + +**Example** + +```dot +my_median_task [type="median" + values=<[ $(fetch1), $(fetch2), $(fetch3), $(fetch4), $(fetch5), $(fetch6), $(fetch7), $(fetch8) ]> + allowedFaults=3] +``` + +Given a `values` array containing `[ 2, 5, 2, "foo", "foo" "bar", "foo", 2 ]`, the task will return: + +```js +{ + "results": [ 2, "foo" ], + "occurrences": 3 +} +```` diff --git a/docs/Oracle Jobs/Task Types/task_multiply.md b/docs/Oracle Jobs/Task Types/task_multiply.md new file mode 100644 index 00000000000..89caf7a660c --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_multiply.md @@ -0,0 +1,34 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Multiply Task" +permalink: "docs/jobs/task-types/multiply/" +--- + +Multiplies the provided `input` and `times` values. + +**Parameters** + +- `input`: the value to be multipled. Possible values: + - number + - stringified number + - bytes-ified number + - `$(variable)` +- `times`: the value to multiply the input with. + - number + - stringified number + - bytes-ified number + - `$(variable)` + +**Outputs** + +The result of the multiplication. + +**Example** + +```dot +my_multiply_task [type="multiply" input="$(json_parse_result)" times=3] +``` + +Given the input `10`, the task will return `30`. + diff --git a/docs/Oracle Jobs/Task Types/task_sum.md b/docs/Oracle Jobs/Task Types/task_sum.md new file mode 100644 index 00000000000..1147319a940 --- /dev/null +++ b/docs/Oracle Jobs/Task Types/task_sum.md @@ -0,0 +1,27 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Sum Task" +permalink: "docs/jobs/task-types/sum/" +--- + +Accepts multiple numerical inputs and returns the sum of them. + +**Parameters** + +- `values`: an array of values to sum. +- `allowedFaults` (optional): the maximum number of input tasks that can error without the Sum task erroring. If not specified, this value defaults to `N - 1`, where `N` is the number of inputs. + +**Outputs** + +The sum of the values in the `values` array. + +**Example** + +```dot +my_sum_task [type="sum" + values=<[ $(fetch1), $(fetch2), $(fetch3) ]> + allowedFaults=1] +``` + +Given the inputs `2`, `5`, and `20`, the task will return `27`. diff --git a/docs/Oracle Jobs/Task Types/tasks.md b/docs/Oracle Jobs/Task Types/tasks.md new file mode 100644 index 00000000000..5c0ff6e751b --- /dev/null +++ b/docs/Oracle Jobs/Task Types/tasks.md @@ -0,0 +1,34 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Tasks" +permalink: "docs/tasks/" +--- + +## What is a Task? + +> ✅ NOTE +> This page refers to tasks in the latest version of Chainlink jobs (otherwise known as TOML, or v2 jobs). For documentation on the legacy job format, see [v1 job specs](/docs/job-specifications/). + +A task is essentially the equivalent of the old [adapters](/docs/core-adapters/) but more flexible. Tasks can be composed in arbitrary order into [pipelines](/docs/jobs/task-types/pipelines/). Pipelines consist of one or more threads of execution where tasks are executed in a well-defined order. + +Chainlink has a number of built-in tasks which are listed below. You can also create your own [external adapters](/docs/external-adapters/) for tasks which are accessed through a `bridge`. + +## Shared attributes + +All tasks share a few common attributes: + +`index`: when a task has more than one input (or the pipeline overall needs to support more than one final output), and the ordering of the values matters, the index parameter can be used to specify that ordering. + +```dot +data_1 [type="http" method="get" url="https://chain.link/eth_usd" index=0] +data_2 [type="http" method="get" url="https://chain.link/eth_dominance" index=1] +multiword_abi_encode [type="eth_abi_encode" method="fulfill(uint256,uint256)"] + +data_1 -> multiword_abi_encode +data_2 -> multiword_abi_encode +``` + +`timeout`: The maximum duration that the task is allowed to run before it is considered to be errored. Overrides the `maxTaskDuration` value in the job spec. + + diff --git a/docs/Oracle Jobs/adapters.md b/docs/Oracle Jobs/adapters.md index f234f988124..05f2427cad9 100644 --- a/docs/Oracle Jobs/adapters.md +++ b/docs/Oracle Jobs/adapters.md @@ -5,6 +5,16 @@ title: "Core Adapters" permalink: "docs/core-adapters/" whatsnext: {"Introduction to External Adapters":"/docs/external-adapters/", "Initiators":"/docs/initiators/"} --- + +# DEPRECATED + +> ⚠️ NOTE +> The style of adapter described below (otherwise known as a JSON adapter or v1 adapter) is deprecated and will be removed in Chainlink 1.0.0. +> +> Please refer to [v2 job tasks](/docs/tasks) instead. + +## Adapters + Core adapters are the built-in functionality that every Chainlink node supports. Strung together, they act as tasks that need to be performed to complete a Job. Adapters that are prefixed with "Eth" refer to tasks that post data onto the chain. Here are some examples of the data types that adapters convert data to. @@ -182,7 +192,8 @@ req.add("extPath", "price/BTC/USD"); } ``` -NOTE: For security, since the URL may come from an untrusted source, HTTPGet imposes some restrictions on which IPs may be fetched. Local network and multicast IPs are disallowed by default and attempting to connect will result in an error. +> ⚠️ NOTE +> For security, since the URL may come from an untrusted source, HTTPGet imposes some restrictions on which IPs may be fetched. Local network and multicast IPs are disallowed by default and attempting to connect will result in an error. If you really must access one of these IPs, you can use the `HTTPGetWithUnrestrictedNetworkAccess` adapter instead. @@ -226,7 +237,8 @@ req.add("extPath", "price/BTC/USD"); } ``` -NOTE: For security, since the URL may come from an untrusted source, HTTPPost imposes some restrictions on which IPs may be fetched. Local network and multicast IPs are disallowed by default and attempting to connect will result in an error. +> ⚠️ NOTE +> For security, since the URL may come from an untrusted source, HTTPPost imposes some restrictions on which IPs may be fetched. Local network and multicast IPs are disallowed by default and attempting to connect will result in an error. If you really must access one of these IPs, you can use the `HTTPPostWithUnrestrictedNetworkAccess` adapter instead. @@ -386,93 +398,3 @@ req.addUint("until", now + 1 hours); ] } ``` - -## Resultcollect - -The core adapter will collect a response from another adapter for transmitting after the rest of the pipeline completes. -#### Parameters - -*None* - -#### Job Specification example - - -```JSON -{ - "initiators": [ - { - "type": "runlog" - } - ], - "tasks": [ - { - "type": "httpget", - "params": { - "get": "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,JPY,EUR" - } - }, - { - "type": "jsonparse", - "params": { - "path": [ - "USD" - ] - } - }, - { - "type": "multiply", - "params": { - "times": 100 - } - }, - { - "jobSpecId": "bcf76dc27fd4484fab681d3f239cd2c3", - "type": "ethuint256" - }, - { - "jobSpecId": "bcf76dc27fd4484fab681d3f239cd2c3", - "type": "resultcollect" - }, - { - "type": "httpget", - "params": { - "get": "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD,JPY,EUR" - } - }, - { - "type": "jsonparse", - "params": { - "path": [ - "EUR" - ] - } - }, - { - "type": "multiply", - "params": { - "times": 100 - } - }, - { - "jobSpecId": "bcf76dc27fd4484fab681d3f239cd2c3", - "type": "ethuint256" - }, - { - "jobSpecId": "bcf76dc27fd4484fab681d3f239cd2c3", - "type": "resultcollect" - }, - { - "type": "ethtx", - "confirmations": 1, - "params": { - "abiEncoding": [ - "bytes32", - "bytes32", - "bytes32" - ] - } - } - ] -} -``` - diff --git a/docs/Oracle Jobs/initiators.md b/docs/Oracle Jobs/initiators.md index 189ca769866..d923cc4fd79 100644 --- a/docs/Oracle Jobs/initiators.md +++ b/docs/Oracle Jobs/initiators.md @@ -6,6 +6,22 @@ permalink: "docs/initiators/" whatsnext: {"Introduction to External Initiators":"/docs/external-initiators-introduction/"} --- +# DEPRECATED + +> ⚠️ NOTE +> Initiators are deprecated and will be removed in Chainlink 1.0.0. +> +> Please refer to [v2 jobs](/docs/jobs) instead. + +## Initiators + +[block:api-header] +{ + "title": "Cron" +} +[/block] +The `Cron` initiator is a simple way to schedule recurring job runs, using standard cron syntax. + ## Cron The `Cron` initiator is a simple way to schedule recurring job runs, using [standard cron syntax](https://en.wikipedia.org/wiki/Cron#Overview). @@ -181,7 +197,8 @@ A new run created by a `RunLog` is automatically given the parameters needed for The `Web` initiator enables jobs to be triggered via web requests, specifically `POST`s to `/v2/specs/:jobID/runs`. Requests coming in to create new job runs must be authenticated by cookie. -NOTE: For convenience, there is a "Run" button in the operator web UI for the job which will trigger a job run. +> ℹℹ NOTE +> For convenience, there is a "Run" button in the operator web UI for the job which will trigger a job run. ### Web Parameters @@ -195,4 +212,4 @@ NOTE: For convenience, there is a "Run" button in the operator web UI for the jo "type": "web" } ] -``` \ No newline at end of file +``` diff --git a/docs/Oracle Jobs/job-specifications.md b/docs/Oracle Jobs/job-specifications.md index e4d934c0e6c..f847983171b 100644 --- a/docs/Oracle Jobs/job-specifications.md +++ b/docs/Oracle Jobs/job-specifications.md @@ -5,6 +5,14 @@ title: "Job Specifications" permalink: "docs/job-specifications/" whatsnext: {"Core Adapters":"/docs/core-adapters/", "Initiators":"/docs/initiators/"} --- + +# DEPRECATED + +> ⚠️ NOTE +> The style of job spec described below (otherwise known as JSON specs or v1 specs) is deprecated and support for it will be removed in Chainlink 1.0.0. If you are still running this type of job, you should migrate them to v2 specs. +> +> Please refer to the [v2 jobs migration page](/docs/jobs/migration-v1-v2) for guidance on how to do this. + ## What is a job? Job specifications, or specs, contain the sequential tasks that the node must perform to produce a final result. Chainlink jobs are divided into 2 segments. @@ -69,4 +77,4 @@ Note: If specifying multiple adapters of the same type, the parameters can be sp Additional parameters may be specified on the job as well. These include: - **startAt**: The beginning date at which the job can be executed, specified in ISO 8601 standard. Jobs can not be ran before this date. Defaults to null if unspecified. - **endAt**: The ending date at which the job can be executed, specified in ISO 8601 standard. Jobs can not be ran after this date. Defaults to null if unspecified. -- **minPayment**: The payment amount for this job, specified in LINK to the 18th decimal. If supplied, this will override the global `MIN_CONTRACT_PAYMENT` configuration set on the node, regardless if the value is lower or higher. \ No newline at end of file +- **minPayment**: The payment amount for this job, specified in LINK to the 18th decimal. If supplied, this will override the global `MIN_CONTRACT_PAYMENT` configuration set on the node, regardless if the value is lower or higher. diff --git a/docs/Oracle Jobs/jobs.md b/docs/Oracle Jobs/jobs.md new file mode 100644 index 00000000000..f4d32219c5d --- /dev/null +++ b/docs/Oracle Jobs/jobs.md @@ -0,0 +1,61 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "v2 Jobs" +permalink: "docs/jobs/" +--- + +## What is a Job? + +> ✅ NOTE +> This section refers to the latest version of Chainlink jobs (v2 jobs, or as seen in the Operator UI interface, TOML jobs). For documentation on the legacy job format, see [v1 job specs](/docs/job-specifications). Note that v1 jobs are deprecated. + +Chainlink nodes require jobs to be added to do anything useful, for example, posting asset price data on-chain. The supported job types are: + +- [`cron`](types/cron/) +- [`webhook`](types/webhook/) +- [`directrequest`](types/direct-request/) +- [`fluxmonitor`](types/flux-monitor/) +- [`keeper`](types/keeper/) +- [`offchainreporting`](types/offchain-reporting/) + +Jobs are represented by TOML specifications. + +--- + +## Example v2 job spec + +The following is an example `cron` job spec, one of the simplest specs that can be added to the node: + +```toml +type = "cron" +schemaVersion = 1 +schedule = "CRON_TZ=UTC 0 0 1 1 *" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F46" +observationSource = """ +ds [type=http method=GET url="https://chain.link/ETH-USD"]; +ds_parse [type=jsonparse path="data,price"]; +ds_multiply [type=multiply times=100]; +ds -> ds_parse -> ds_multiply; +""" +``` + + +## Shared fields + +The following TOML fields are shared by every job type supported by the node. + +- `name`: the name of the job in the Operator UI. +- `type`: specifies the v2 job type. Can be one of the following: + - `cron` + - `webhook` + - `directrequest` + - `fluxmonitor` + - `keeper` + - `offchainreporting` +- `schemaVersion`: not currently used, but must be present and set to a value of `1`. Will allow us to handle progressive iterations of the job spec format gracefully with backwards-compatibility. +- `observationSource`: the v2 pipeline task DAG. Specified in DOT syntax. See below for information on writing pipeline DAGs. +- `maxTaskDuration`: the default maximum duration that any given task is allowed to run. If the duration is exceeded, the task is errored. This value can be overridden per-task (see subsequent sections). +- `externalJobID`: The externalJobID is an optionally user-specified way to deterministically provide an ID of a job. If provided, it must be unique. Say for example you want to run the same directrequest job on two different chainlink nodes which have different bridge names. Although the actually spec contents differ slightly, you can use the same externalJobID on both, specify that in your on-chain requests and both nodes will pick it up. + - If you do not provide one, one will be generated by the node. + diff --git a/docs/Oracle Jobs/migration-v1-v2.md b/docs/Oracle Jobs/migration-v1-v2.md new file mode 100644 index 00000000000..6ebbdd2a7ea --- /dev/null +++ b/docs/Oracle Jobs/migration-v1-v2.md @@ -0,0 +1,298 @@ +--- +layout: nodes.liquid +date: Last Modified +title: "Migrating to v2 Jobs" +permalink: "docs/jobs/migration-v1-v2/" +--- +There have been two types of jobs supported by Chainlink nodes prior to [version 1.0.0](https://github.com/smartcontractkit/chainlink/blob/develop/docs/CHANGELOG.md). There are [v1 jobs](/docs/job-specifications/) (also known as JSON jobs) and [v2 jobs](/docs/jobs/) (also known as TOML jobs). + +The original v1 jobs **are now deprecated**, and developers should migrate to v2 jobs as soon as possible. v2 jobs are much more powerful, supporting advanced capabilities like running tasks in parallel. + +## Comparison between v1 and v2 jobs + +v1 jobs were intended for extremely targeted use cases, and as such, they opted for simplicity in the job spec over explictness. + +v2 jobs were designed with an awareness of the rapid expansion of functionality supported by the Chainlink node, as well as the ever-increasing complexity of jobs. As such, they prefer explicitness. + +### DAG dependencies and variables + +v2 jobs require the author to specify dependencies using [DOT syntax](https://en.wikipedia.org/wiki/DOT_(graph_description_language)). If a task needs data produced by another task, this must be explicitly specified using DOT. + +Additionally, to facilitate explicitness, v2 jobs require the author to specify inputs to tasks using `$(variable)` syntax. + +For example, if an `http` task should feed data into a `jsonparse` task, it should be specified as such: + +```jpv2dot +fetch [type=http method=get url="http://chain.link/price_feeds/ethusd"] + +# This task consumes the output of the 'fetch' task in its 'data' parameter +parse [type=jsonparse path="data,result" data="$(fetch)"] + +# This is the specification of the dependency +fetch -> parse +``` + +The output of each task is stored in the variable corresponding to that task's name (the portion of each task definition before the opening `[` bracket). In some cases, tasks return complex values (i.e., maps or arrays). Using dot access syntax, you can access the elements of these values. For example: + +```jpv2dot +// Assume that this task returns the following object: +// { "ethusd": 123.45, "btcusd": 678.90 } +parse [type=jsonparse path="data" data="$(fetch)"] + +// Now, we want to send the ETH/USD price to one bridge and the BTC/USD price to another: +submit_ethusd [type="bridge" name="ethusd" requestData=<{ "value": $(parse.ethusd) }>] +submit_btcusd [type="bridge" name="btcusd" requestData=<{ "value": $(parse.btcusd) }>] + +parse -> submit_ethusd +parse -> submit_btcusd +``` + +### Quotes + +Some tasks, like the `bridge` tasks above, require you to specify a JSON object. Because the keys of JSON objects must be enclosed in double quotes, you must use the alternative `<` angle bracket `>` quotes. Angle brackets also enable multi-line strings, which can be useful when a JSON object parameter is large: + +```jpv2dot +submit_btcusd [type="bridge" + name="btcusd" + requestData=<{ + "value": $(foo), + "price": $(bar), + "timestamp": $(baz) + }>] +``` + + +### Misc. notes + +- Each job type provides a particular set of variables to its pipeline. See the documentation for each job type to understand which variables are provided. +- Each task type provides a certain kind of output variable to other tasks that consume it. See the documentation for each task type to understand their output types. + +--- + +## Example Migrations + +### Runlog with ETH ABI encoding + +**v1 spec** + +This spec relies on CBOR encoded on-chain values for the `httpget` URL and `jsonparse` path. + +```js +{ + "name": "Get > Bytes32", + "initiators": [ + { + "type": "runlog", + "params": { + "address": "YOUR_ORACLE_CONTRACT_ADDRESS" + } + } + ], + "tasks": [ + { + "type": "httpget" + }, + { + "type": "jsonparse" + }, + { + "type": "ethbytes32" + }, + { + "type": "ethtx" + } + ] +} +``` + +Notes: +- In v1, the job ID is randomly generated at creation time. In v2 it can either be automatically generated or manually specified. +- The `ethbytes32` task (any all of the other ABI encoding tasks) is now encapsulated within the `ethabiencode` task with much more flexibility. Please see [the docs for this task](/docs/jobs/task-types/eth-abi-encode/). + + + +**Equivalent v2 spec:** + + +```jpv2 +type = "directrequest" +schemaVersion = 1 +name = "Get > Bytes32" +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F47" # OPTIONAL - if left unspecified, a random value will be automatically generated +observationSource = """ + decode_log [type=ethabidecodelog + abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)"] + + decode_cbor [type=cborparse data="$(decode_log.data)"] + fetch [type=http method=get url="$(decode_cbor.url)"] + parse [type=jsonparse path="$(decode_cbor.path)"] + encode_data [type=ethabiencode abi="(uint256 value)" data=<{ "value": $(parse) }>] + encode_tx [type=ethabiencode + abi="fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data)" + data=<{ + "requestId": $(decode_log.requestId), + "payment": $(decode_log.payment), + "callbackAddress": $(decode_log.callbackAddr), + "callbackFunctionId": $(decode_log.callbackFunctionId), + "expiration": $(decode_log.cancelExpiration), + "data": $(encode_data) + }>] + submit [type=ethtx to="$(jobSpec.contractAddress)" data="$(encode_tx)"] + + decode_log -> decode_cbor -> fetch -> parse -> encode_data -> encode_tx -> submit +""" +``` + +### Simple fetch (runlog) + +**v1 spec:** + +```js +{ + "initiators": [ + { + "type": "RunLog", + "params": { "address": "0x51DE85B0cD5B3684865ECfEedfBAF12777cd0Ff8" } + } + ], + "tasks": [ + { + "type": "HTTPGet", + "params": { "get": "https://bitstamp.net/api/ticker/" } + }, + { + "type": "JSONParse", + "params": { "path": [ "last" ] } + }, + { + "type": "Multiply", + "params": { "times": 100 } + }, + { "type": "EthUint256" }, + { "type": "EthTx" } + ], + "startAt": "2020-02-09T15:13:03Z", + "endAt": null, + "minPayment": "1000000000000000000" +} +``` + +**v2 spec:** + +```jpv2 +type = "directrequest" +schemaVersion = 1 +name = "Get > Bytes32" +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F47" +observationSource = """ + decode_log [type=ethabidecodelog + abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)" + data="$(jobRun.logData)" + topics="$(jobRun.logTopics)"] + + fetch [type=http method=get url="https://bitstamp.net/api/ticker/"] + parse [type=jsonparse data="$(fetch)" path="last"] + multiply [type=multiply input="$(parse)" times=100] + encode_data [type=ethabiencode abi="(uint256 value)" data=<{ "value": $(multiply) }>] + encode_tx [type=ethabiencode + abi="fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data)" + data=<{ + "requestId": $(decode_log.requestId), + "payment": $(decode_log.payment), + "callbackAddress": $(decode_log.callbackAddr), + "callbackFunctionId": $(decode_log.callbackFunctionId), + "expiration": $(decode_log.cancelExpiration), + "data": $(encode_data) + }>] + send_tx [type=ethtx to="$(jobSpec.contractAddress)" data="$(encode_tx)"] + + decode_log -> fetch -> parse -> multiply -> encode_data -> encode_tx -> send_tx +""" +``` + +### Cron + +**v1 spec:** + +```js +{ + "initiators": [ + { + "type": "cron", + "params": { "schedule": "CRON_TZ=UTC * */20 * * * *" } + } + ], + "tasks": [ + { + "type": "HttpGet", + "params": { "get": "https://example.com/api" } + }, + { + "type": "JsonParse", + "params": { "path": [ "data", "price" ] } + }, + { + "type": "Multiply", + "params": { "times": 100 } + }, + { + "type": "EthUint256" + }, + { + "type": "EthTx" + } + ] +} +``` + +**v2 spec:** + +```jpv2 +type = "cron" +schemaVersion = 1 +schedule = "CRON_TZ=UTC * */20 * * * *" +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F46" +observationSource = """ + fetch [type=http method=GET url="https://example.com/api"] + parse [type=jsonparse data="$(fetch)" path="data,price"] + multiply [type=multiply input="$(parse)" times=100] + encode_tx [type=ethabiencode + abi="submit(uint256 value)" + data=<{ "value": $(multiply) }>] + submit_tx [type=ethtx to="0x859AAa51961284C94d970B47E82b8771942F1980" data="$(encode_tx)"] + + fetch -> parse -> multiply -> encode_tx -> submit_tx +""" +``` + +### Web (-> Webhook) + +**v1 spec:** + +```js +{ + "initiators": [{"type": "web"}], + "tasks": [ + {"type": "multiply", "params": {"times": 100}}, + {"type": "custombridge"} + ] +} +``` + +**v2 spec:** + +```jpv2 +type = "webhook" +schemaVersion = 1 +externalJobID = "0EEC7E1D-D0D2-476C-A1A8-72DFB6633F46" +observationSource = """ + multiply [type=multiply input="$(jobRun.requestBody)" times=100] + send_to_bridge [type=bridge name="custombridge" requestData="$(multiply)"] + + multiply -> send_to_bridge +""" +``` diff --git a/docs/Using Randomness/vrf-security-considerations.md b/docs/Using Randomness/vrf-security-considerations.md index cf6d7af671b..b60e1f1fb69 100644 --- a/docs/Using Randomness/vrf-security-considerations.md +++ b/docs/Using Randomness/vrf-security-considerations.md @@ -65,7 +65,7 @@ Otherwise, the cryptoeconomic security properties may be violated by an attacker If your fulfillRandomness implementation reverts, the VRF service will not attempt to call it a second time. Make sure your contract logic does not revert. Consider simply storing the randomness and taking more complex follow-on actions in separate contract calls made by you or your users. -> ✅NOTE +> ✅ NOTE > If your implementation does revert, you can independently re-send the fullfilment transaction to ensure that all requests issued by your contract are fulfilled, but the VRF service won't do so on your behalf.) ## Use `VRFConsumerBase` in your contract, to interact with the VRF service