{ "kind": "open-flow-workflow", "version": 1, "name": "Alert on Bitcoin Euro Price by SMS", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_79ef1304ecd2675b": { "name": "Every minute", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "* * * * *", "timezone": "UTC" } ] }, "n_3f58dc3bbe8fb41c": { "inputs": { "ids": { "kind": "value", "value": "bitcoin" }, "vs_currencies": { "kind": "value", "value": "eur" } }, "name": "Get Bitcoin price in EUR", "kind": "task", "task": { "inputs": [ { "description": "Target currency of coins, comma-separated if querying more than 1 currency. \n*refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies)", "jsonSchema": { "type": "string", "minLength": 1, "description": "Target currency of coins, comma-separated if querying more than 1 currency. \n*refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies)" }, "nullable": false, "handle": "vs_currencies" }, { "description": "Coins' IDs, comma-separated if querying more than 1 coin. \n*refers to [`/coins/list`](/reference/coins-list)", "jsonSchema": { "type": "string", "minLength": 1, "description": "Coins' IDs, comma-separated if querying more than 1 coin. \n*refers to [`/coins/list`](/reference/coins-list)" }, "nullable": true, "handle": "ids", "value": null }, { "description": "Coins' names, comma-separated if querying more than 1 coin.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Coins' names, comma-separated if querying more than 1 coin." }, "nullable": true, "handle": "names", "value": null }, { "description": "Coins' symbols, comma-separated if querying more than 1 coin.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Coins' symbols, comma-separated if querying more than 1 coin." }, "nullable": true, "handle": "symbols", "value": null }, { "description": "For `symbols` lookups, specify `all` to include all matching tokens. \nDefault `top` returns top-ranked tokens by market cap or volume.", "jsonSchema": { "type": "string", "enum": [ "top", "all" ], "description": "For `symbols` lookups, specify `all` to include all matching tokens. \nDefault `top` returns top-ranked tokens by market cap or volume." }, "nullable": true, "handle": "include_tokens", "value": null }, { "description": "Include market capitalization. \nDefault: false", "jsonSchema": { "type": "boolean", "description": "Include market capitalization. \nDefault: false" }, "nullable": true, "handle": "include_market_cap", "value": null }, { "description": "Include 24-hour trading volume. \nDefault: false", "jsonSchema": { "type": "boolean", "description": "Include 24-hour trading volume. \nDefault: false" }, "nullable": true, "handle": "include_24hr_vol", "value": null }, { "description": "Include 24-hour change percentage. \nDefault: false", "jsonSchema": { "type": "boolean", "description": "Include 24-hour change percentage. \nDefault: false" }, "nullable": true, "handle": "include_24hr_change", "value": null }, { "description": "Include last updated price time as a UNIX timestamp. \nDefault: false", "jsonSchema": { "type": "boolean", "description": "Include last updated price time as a UNIX timestamp. \nDefault: false" }, "nullable": true, "handle": "include_last_updated_at", "value": null }, { "description": "Decimal places for currency price value", "jsonSchema": { "type": "string", "enum": [ "full", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18" ], "description": "Decimal places for currency price value" }, "nullable": true, "handle": "precision", "value": null } ], "outputs": [ { "description": "Coin selectors mapped to quotes; include_tokens=all may return an array of token quotes for each symbol.", "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "object", "properties": {}, "additionalProperties": true, "description": "Quote currency prices and optional market metrics. Keys are currency IDs, metric suffixes and optional last_updated_at; values may be null when unavailable." }, { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Quote currency prices and optional market metrics. Keys are currency IDs, metric suffixes and optional last_updated_at; values may be null when unavailable." }, "description": "Matching token quotes." } ], "description": "One coin quote or multiple token quotes." }, "description": "Coin selectors mapped to quotes; include_tokens=all may return an array of token quotes for each symbol." }, "nullable": false, "handle": "prices" } ], "name": "Get Bitcoin price in EUR", "executor": { "kind": "connector", "action": "coingecko.get_prices" } } }, "n_b04cf546748b1f4f": { "inputs": { "prices": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3f58dc3bbe8fb41c", "output": "prices" } ] }, "threshold": { "kind": "value", "value": 9000 }, "to": { "kind": "value", "value": "YOUR_RECIPIENT_NUMBER" }, "from": { "kind": "value", "value": "YOUR_TWILIO_NUMBER" } }, "name": "Check the threshold", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "object", "properties": {}, "additionalProperties": true, "description": "Quote currency prices and optional market metrics. Keys are currency IDs, metric suffixes and optional last_updated_at; values may be null when unavailable." }, { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Quote currency prices and optional market metrics. Keys are currency IDs, metric suffixes and optional last_updated_at; values may be null when unavailable." }, "description": "Matching token quotes." } ], "description": "One coin quote or multiple token quotes." }, "description": "Coin selectors mapped to quotes; include_tokens=all may return an array of token quotes for each symbol." }, "nullable": false, "handle": "prices" }, { "jsonSchema": { "type": "number", "minimum": 0 }, "nullable": false, "handle": "threshold" }, { "jsonSchema": { "type": "string", "description": "The destination phone number in E.164 format." }, "nullable": false, "handle": "to" }, { "jsonSchema": { "type": "string", "description": "The Twilio phone number sending the message." }, "nullable": false, "handle": "from" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "matched" }, { "jsonSchema": { "type": "string", "description": "The destination phone number in E.164 format." }, "nullable": false, "handle": "to" }, { "jsonSchema": { "type": "string", "description": "The Twilio phone number sending the message." }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "string", "description": "The text body of the outbound message." }, "nullable": false, "handle": "message" } ], "name": "Check the threshold", "moduleId": "n_bbd6ccfe93ddcedb", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_c958322e5e4f6861": { "inputs": {}, "name": "Price at or above threshold", "kind": "condition", "matchMode": "first", "cases": [ { "output": "alert", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_b04cf546748b1f4f", "output": "matched" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_b71945b0ef2ff758": { "inputs": { "to": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b04cf546748b1f4f", "output": "to" } ] }, "from": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b04cf546748b1f4f", "output": "from" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b04cf546748b1f4f", "output": "message" } ] } }, "name": "Send an SMS alert", "kind": "task", "task": { "inputs": [ { "description": "The destination phone number in E.164 format.", "jsonSchema": { "type": "string", "description": "The destination phone number in E.164 format." }, "nullable": false, "handle": "to" }, { "description": "The Twilio phone number sending the message.", "jsonSchema": { "type": "string", "description": "The Twilio phone number sending the message." }, "nullable": false, "handle": "from" }, { "description": "The text body of the outbound message.", "jsonSchema": { "type": "string", "description": "The text body of the outbound message." }, "nullable": false, "handle": "body" } ], "outputs": [ { "description": "The Twilio message SID.", "jsonSchema": { "type": "string", "description": "The Twilio message SID." }, "nullable": false, "handle": "messageSid" }, { "description": "The Twilio account SID that owns the message.", "jsonSchema": { "type": [ "string", "null" ], "description": "The Twilio account SID that owns the message." }, "nullable": true, "handle": "accountSid" }, { "description": "The delivery status of the message.", "jsonSchema": { "type": [ "string", "null" ], "description": "The delivery status of the message." }, "nullable": true, "handle": "status" }, { "description": "The destination phone number.", "jsonSchema": { "type": [ "string", "null" ], "description": "The destination phone number." }, "nullable": true, "handle": "to" }, { "description": "The sender phone number.", "jsonSchema": { "type": [ "string", "null" ], "description": "The sender phone number." }, "nullable": true, "handle": "from" }, { "description": "The text body of the message.", "jsonSchema": { "type": [ "string", "null" ], "description": "The text body of the message." }, "nullable": true, "handle": "body" } ], "name": "Send an SMS alert", "executor": { "kind": "connector", "action": "twilio.send_message" } } } }, "edges": [ { "source": "n_79ef1304ecd2675b", "target": "n_3f58dc3bbe8fb41c" }, { "source": "n_3f58dc3bbe8fb41c", "target": "n_b04cf546748b1f4f" }, { "source": "n_b04cf546748b1f4f", "target": "n_c958322e5e4f6861" }, { "source": "n_c958322e5e4f6861", "target": "n_b71945b0ef2ff758", "sourceHandle": "alert" } ] } }, "modules": { "n_bbd6ccfe93ddcedb": { "name": "Check the threshold", "imports": [], "source": "export default function alert({prices,threshold,to,from}) {\n const price=prices?.bitcoin?.eur;\n if(typeof price!=='number'||!Number.isFinite(price)||price<0)throw Error('CoinGecko returned no valid Bitcoin EUR price');\n if(typeof threshold!=='number'||!Number.isFinite(threshold)||threshold<0)throw Error('Set a nonnegative EUR threshold');\n for(const n of [to,from])if(typeof n!=='string'||!/^\\+[1-9]\\d{7,14}$/.test(n))throw Error('Set Twilio numbers in E.164 format');\n return {matched:price>=threshold,to,from,message:`The price went up! The new price is ${price}`};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }