The relayer is responsible for managing on-chain Identities (called "Ambire accounts" in public communication) and relaying gasless transactions to the Ethereum network.
It is not intended to be blockchain-agnostic, and it is Ethereum-specific.
npm i --legacy-peer-deps
# it's important to set NODE_ENV=development cause that tells the relayer to use localhost:1934 for the URLs in the emails
NODE_ENV=development npm start
NOTE: Add rewards collection to adexRelayer database. Two records are needed with _ids "adx-rewards" and "balance-rewards" for the wallet to run properly.
In adex-rewards collection:
{
"_id" : "adx-rewards",
"rewards" : {
"0x0000000000000000000000000000000000000001" : 12345.6789,
...
},
"updated" : ISODate("2023-1-1T12:00:00.0Z")
}
In balance-rewards collection:
{
"_id" : "balance-rewards",
"rewards" : {
"0x0000000000000000000000000000000000000002" : 420.691234,
...
},
"updated" : ISODate("2023-1-1T12:00:00.0Z")
}
NOTE: Your MongoDB GUI might not recognise it as JSON. In this case remove the "updated" field for temporary fix.
docker build . --tag=adexnetwork/relayer
Note that there's automatic building set up for adexnetwork/relayer.
PRIVATE_KEYPORTMONGO_URLMONGO_DB_NAMEEMAIL_SOURCE: set to any email source configured in Amazon SES, in order to enable notification emails; See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html on how to load AWS credentials; recommended value is [email protected]AWS_REGION: AWS regionOCTOPUSEMAIL_API_URL: Еmail Оctopus API endpointOCTOPUSEMAIL_API_KEY: Еmail Оctopus API KeyOCTOPUSEMAIL_LIST_ID: Еmail Оctopus listID for welcome listLOCK_INTERVAL: Lock interval in seconds for brute force prevention (default is 60 seconds)LOCK_TRIES: Available tries per minute before lock (default is 20 tries)RATE_LIMIT_ENABLE: If the env is set, rate limits will be applied to all routes in the service.For npm run monitor only:
WALLET_ADDRPUSHOVER_USERPUSHOVER_TOKENcounters collection from MongoDB to avoid having to go through all blocks until the latest blocknode ./scripts/grant.js [mainnet|goerli] [DAI|SAI|TST] amount email
Or, if you want to send invitation emails, use:
[email protected] node scripts/grant.js [mainnet|goerli] [DAI|SAI|TST] amount email
For example:
[email protected] node scripts/grant.js mainnet SAI 5 [email protected]
You can also invite users (send them the email without issuing a grant) using:
[email protected] node scripts/invite.js email
For more information on what those mean, see https://github.com/AmbireNetwork/adex-protocol/issues/10
Send emails after successfully creating Identity contracts, that inform the user that, for example "Your account has been created"
Each Ambire account (publisher/advertiser) will have their own Identity contract, which is similar to a multi-sig contract that allows this user to interact with the network through various devices/methods of authentication (e.g. phone, browser, Trezor, Ledger). All those keypairs (called "owners") can have different privilege levels: Routines, Transactions and Withdraw.
The Routines privilege level allows to withdraw funds from OUTPACE channels to the Identity contract.
The Transactions privilege level allows any arbitrary transaction to be sent from the Identity contract.
The Withdraw privilege level allows lower privilege levels (Routines/Transactions) to withdraw from the Identity contract to the address with Withdraw privileges. You may want to use that with a hardware wallet (e.g. Trezor), thereby being able to withdraw to your Trezor without having it plugged in.
The Identity contract for each user only gets deployed upon their first interaction with the blockchain.
The address of this contract is pre-calculated, meaning you can start receiving funds. Once you want to withdraw those funds or start a campaign ("first interaction with the blockchain"), the Identity contract will be deployed.
This is a UX advantage because you do not need to pay any blockchain fees (in this case, to deploy the Identity contract) to start using Ambire. For the tech decisions, see the rough notes, changes in adex-protocol-eth and and the changes report.
The relayer allows grant winners to claim their pre-funded accounts.
Grant distribution happens upon confirming your account's email, by simply sending DAI to the identity address.
Any transaction can be executed through the Identity contract, as long as the authorized owner has signed it, via the execute method.
That way, the Relayer can pay the gas for executing this transaction, while being rewarded with tokens for doing so.
This allows owners that don't have ether to interact with the Identity contract.
Any owner with a Transactions (or higher) privilege can authorize the relayer to perform "chores" on their behalf. For example, withdrawing from channels, or opening campaigns.
This is similar to transactions, but includes a limited set of operations. For example, withdrawing OUT of the Identity contract is not possible, but withdrawing from channels INTO the Identity contract (called "sweeping") is allowed.
The most powerful advantage here is enabling automatic channel withdrawals: since each OUTPACE channel has a validUntil date, you have to withdraw your earnings before that time. The relayer will do this for you.
NOTE: this is not implemented yet; instead, we use a fixed HW wallet address that can recover accounts
The Ambire platform will allow "Quick signup", which is a signup mode that only requires a valid email and creates an in-browser keypair automatically (called "Quick accounts" or "Quick wallets").
The recovery service will be automatically added as an owner to each Identity that's created with an in-browser keypair as an owner only.
This way, if the user loses this keypair, the recovery service can intervene, if the user proves control of the email they registered with.
This works the same way as any classic email/password-based login. When/if the user adds a proper authentication method to their Identity (Metamask, Trezor, Ledger), the recovery service will be removed. Alternatively, the user can choose to remove the recovery service themselves, accepting the risk of losing their funds.
Later on, this can be replaced by a trust-minimized DKIM signature-based solution.
All addresses are stored checksummed, no exceptions.
Defined in services/eth/transactions:
GAS_PRICE_RATIO - the network gas price will be multiplied by this to get the gas price we will use
GAS_PRICE_RATIO_RESUBMIT - the same as GAS_PRICE_RATIO, but used when resubmitting
RESUBMIT_STALLED - boolean - whether re-submitting stalled transactions is enabled
TIME_BEFORE_RESUBMIT - how long do we wait before we re-submit transactions
Those basic methods would be exposed through REST routes.
When you create a new user identity, submit it's original deploy data via POST /identity/:identity.
Then, the relayer will deploy it on one of the following conditions:
execute is requestedexecuteRoutines is triggered by the routineAuthsLoopexecute ({ identityAddr, txnsRaw, signatures, channel })
This method should also evaluate if all signers in the transactions have >=PrivilegeLevel.Transaction privilege level on the Identity contract
If the identity is not deployed, this method will deploy it via the data in the identityDeployData collection. The required fee for the first transaction will be higher to offset the deploy cost.
POST /identity/:identity - stores an identity to the identityDeployData collection; optionally, you can pass email, which will send a welcome email to the user and a confirmation link (which may trigger receiving a grant, in the form of DAI sent to :identity); does not deploy an identity, it simply stores it; will calculate the expectedAddr and return an error if it's different from :identity
GET /identity/:identity/:network - retrieves up to date information about an identity, including it's current privileges; if it's not deployed yet it will return the initial owners (original privileges); would also return isLimitedVolume
GET /identity/:identity/confirm-email/:code - confirms the email for an identity; optional query parameter ?redirect for redirecting the user somewhere after the confirmation; it will also claim all non-claimed coupons for that email; you can use this URL as many times as you wish, even if the email has already been confirmed - which is useful in case you want to claim new coupons
GET /identity/:network/by-owner/:owner - get a list of identities for which the owner address is privileged
GET /email/:email - returns this email is existing ot not
@TODO update this and add others
Executors are responsible for broadcasting individual user bundles to the network, by translating them to actual Ethereum transactions. Executors are also responsible for marking the bundles as confirmed by the network in case they are, marking them as dropped, nonce management, etc.
Initially we started with one executor, mempool, and we later replaced it with mempool V2. Both call the standard RPC method eth_sendTrarnsaction to broadcast transactions, howeverwe can use a privacy preserving RPC such as Flashbots or MEVProtect.
In order to develop a new executor, one should simply develop a new class that follows the MempoolV2`` interface. There is just one public method, processBundles, and the constructor follows this signature: constructor (executorId: string, network: NetworkInfo, privateKey: string)`
Here are some rules that MempoolV2 and the new executor design in general follow:
send that the executor returns: because what we write in the DB can be influenced by sending outcome (in case it fails); also, we no longer do DB update before sending (doesn't make sense, we can do the DB update regardless of whether sending failed still)dbUpdate to "stick" stuff to the userTxns collections, and they'll receive all pending bundles (userTxns) at every cycle (every block), so it's more than enough to store needed contextNOTE: updated for Ambire v2
The scheduler is responsible for picking an executor for each bundle and periodically checking the status of each bundle by calling the executor.
It does this on each new block.
It is also responsible for making sure the same executor stays assigned to a user as long as that user has a pending bundle - this is because an executor needs context for all the user's pending bundles to function properly (rpelace-by-fee, enqueuing, etc.)
NOTE: updated for Ambire v2
The relayer will load a single ethers.js signer that it will use globally. This signer will be indirectly accessible to the routes code through the executors.
Since the routes need to issue transactions, there's no going around this, and the routes code should be carefully crafted to not allow any arbitrary code execution/arbitrary transactions being built.
Even if we tried to separate concerns and only sign in a separate worker, the worker would be blindly getting and signing raw transactions directly from the database, so there is little benefit to introducing this.
NOTE: updated for Ambire v2
The relayer is designed to operate with one Ethereum keypair and execute as many transactions per Ethereum block as possible. Because Ethereum will always be the bottleneck by a long shot, we don't need multiple workers or multiple keypairs.
Something very important is that a single executor instance MUST only processBundles once at a time. Currently, this is implemented via a local p-limit, but if we want to scale the relayer to multiple processes we MUST re-implement this lock to be global via Redis or some alternative.
In case this limitation becomes a bottleneck (really unlikely), the simple way to solve it is via horizontal scaling (#465) - in other words, just adding another executor for the network. The scheduler will manage this and ensure we stick to the same executor for a user as long as that user has a pending bundle.
The mempool V2 executor is re-designed from the ground up with the following benefits:
This collection contains transactions that will be executed on-chain, by the relayer.
The userTxns collection has the following fields:
txId: optioinal string: the EVM transaction ID, set only if it's broadacasted on the networkexecuted.success: optional boolean: only set if the underlying transactiton has been confirimed by the networkThere are additional executor-specific fields, like mempoolV2Current, mempoolV2Pending; TODO
Please note, "executed" object being set simply means the transaction is confirmed on-chain, regardless of whether it was successful or not (e.g. it reverted).
Stores the initial deploy data of each identity.
_id: indexed, unique: the address of the identity; calculated by using generateAddress2(identityFactoryAddr, salt, bytecode)salt: indexed: the salt, stored as a hex stringbytecode: the compiled bytecode, stored as a hex string; generated from baseIdentityAddr, privileges, routineAuthorizations, but it is stored because we may use a different solc version at some pointidentityFactoryAddr: the address of the factory we're about to deploy withSome fields are the underlying arguments passed to getProxyDeployByteCode:
baseIdentityAddr: the address of the base identity contract; this may be used in the Platform to identify whether the identity is v4.0 or v4.1initialPrivileges: the wallets that control this identity; passed as privilegesNOTE: deploy fees are not included in this, since we don't need them with counterfactual deployment; since we only deploy upon executing transactions, we can just raise the first execute fee to offset the cost of deploying
Some fields store extra information:
initialPrivilegesAddrs: indexed: same as privileges but only the addresses, for indexing purposesmeta: unused for nowWhen a transaction fails for seemingly mysterious reasons, HEVM can be used to see details like the call stack, which contract it failed in, etc.
Download HEVM from: https://github.com/dapphub/dapptools/releases
Use HEVM like this, make sure to replace all the template values and the RPC if needed:
export TERMINFO=/lib/terminfo
./hevm exec --caller <txn from> --address <txn to> --calldata <txn data> --rpc http://165.22.74.127:8545 --gas 1000000 --debug
Currently there's no UI for this, but you can:
const secret = require('otplib').authenticator.generateSecret()
console.log(secret)
db.identities.update({ 'meta.email': 'email' }, { $set: { 'secrets.otp': secret } })
To use this setup, you can add the secret to Google Authenticator, Microsoft Authenticator, Yubikey (Yubico Authenticator app)
This script will update token settings in database.
node scripts/update-coin-price-sets.js
Please, when adding a token for update, leave all previous ones to keep history.
WARN: All defined parameters for update will OVERWRITE previous value for the same parameters !!!
To append instand of overwrite you can use notation in string. Example:
[Append] { "param.subparam" : "value" }
[Overwrite] { param : {subparam : "value"} }
updateInfo[coingeckoId] - coingeckoId is id from document.
When script is finish will automaticaly exit and provide result from mongodb operations.
This service can be start with
npm start velcro
API end point and balance method
https://velcro.ambire.com/v1/protocols/${protocol}/balances?addresses=${address}&network=${chain}
protocol can be tokens or nftaddress is wallet addresschain is chain name from cfgnode scripts/token-info-velcro-dump.js > ./src/velcro/data/tokensInfo.json
node scripts/token-info-velcro-update.js
humanizerInfo.json)node scripts/generateContractInfo.js > src/lib/humanizerInfo.json
All banners are configurable from cfg/promos.js
campaigns type:
{
id, // string and should be unique
title, // string
text, // string, also allow to include resources as their param title ex: 'Click here: ${{link_for_tests}}' result will be "Click here: Read now!"
icon, // string emoji
type, // string with opitions 'info' or 'warning'
resources: {
link_for_tests: {
href: 'https://blog.ambire.com/ambire-wallet-2nd-birthday/',
label: 'Read now!'
}
},
period: { from: new Date(), to: new Date(), timer: false } // timer: true will show a banner as countdown
}
campaignsCustomData
In this object can be provided campaign id as param and array of specific Ambire addresses. The specific banner will be visible only for those addresses.
For more information checkout https://github.com/AmbireTech/ambire-common/wiki/Email-Vault-Documentation
EmailVault (EV) is collective title for everything related to email accounts in Ambire. In the context of the relayer, EVs are data records in the emailVaults collections, responsible for managing email accounts. The main point of this is to extract all email-related data from the identities collection and to provide flexibility in storing other types of data, related only to EVs.
Secrets are used for storing sensitive data in the relayer. Secret structure:
{
key: string
value?: string
type: string
password?: string
}
Operations are used for cross-device communication, executed via the relayer Operation structure:
{
key: string
value?: string
type: string
password?: string
}
Content type
Image
Digest
sha256:a58f2aa8d…
Size
737.2 MB
Last updated
about 7 hours ago
docker pull adexnetwork/nft-image-proxy