Transaction
Content Summary
everPay has its own separate transaction format, all everPay transactions follow the same format: Sign the same signature specification, and store them on the arweave blockchain for all to verify.
Schema
Field | Description |
---|---|
tokenSymbol | Token Symbol, AR,ETH,USDT,USDC etc. |
action |
|
from | the current everPay account ID that signed the transaction. |
to |
|
amount | Type uint; decimals processing is required for setting, e.g. 0.1USDT, after USDT's decimals: 6 processing, it 100000.
|
fee | Handling fee, type uint. needs to be decimals, e.g. 0.1USDT, here it's 100000 after USDT's decimals: 6 processing. |
feeRecipient | Receive everPay account ID for handling fees, via info API interface to get. |
nonce | unix milliseconds. |
tokenID | via info API interface, must be consistent with the token id field corresponding to tokenSymbol . |
chainType | chainType must be the same as info API, the token chainType consistent. |
chainID | chainID must be the same as info API, the token chainID consistent. |
data | Additional information, developer-customizable JSON data, processed by JSON.stringify() and passed in. Developers can pass data to customize some complex functions, like Bundle. |
version | transaction version 'v1' . |
Example of an ethereum account
const everpayTxWithoutSig = {
tokenSymbol: 'usdt',
action: 'transfer',
from: '0x26361130d5d6E798E9319114643AF8c868412859',
to: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo',
amount: '5260000',
fee: '0',
feeRecipient: '0x6451eB7f668de69Fb4C943Db72bCF2A73DeeC6B1',
nonce: '1626079771946',
tokenID: '0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee',
chainType: 'ethereum',
chainID: '42',
data: '{"hello":"world","this":"is everpay"}',
version: 'v1'
}
Example of an arweave account
const everpayTxWithoutSig = {
"tokenSymbol": "TUSDC",
"action": "transfer",
"from": "5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo",
"to": "0x26361130d5d6E798E9319114643AF8c868412859",
"amount": "1000000",
"fee": "0",
"feeRecipient": "0xfAC49e12F19743FFc3A756294f1bf70C282E25fA",
"nonce": "1708507073627",
"tokenID": "0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712",
"chainType": "bsc",
"chainID": "97",
"data": "",
"version": "v1"
}
Example of a smart account
const everpayTxWithoutSig = {
"tokenSymbol": "TUSDC",
"action": "transfer",
"from": "eidd92c8451f8c5f1e4ab05ad75bfee0acfd5bbe5e3cf2f99e1fad5d4329fb650bc696b",
"to": "0x26361130d5d6E798E9319114643AF8c868412859",
"amount": "100000",
"fee": "0",
"feeRecipient": "0xfAC49e12F19743FFc3A756294f1bf70C282E25fA",
"nonce": "1708507959655",
"tokenID": "0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712",
"chainType": "bsc",
"chainID": "97",
"data": "",
"version": "v1"
}
messageData
Generated by Schema in a uniform format for.
- Ethereum
personalSign
signature generation. - Generate
everHash
.
Generation rules
export const getEverpayTxMessageData = (everpayTxWithoutSig: EverpayTxWithoutSig): string => {
const keys = [
'tokenSymbol',
'action',
'from',
'to',
'amount',
'fee',
'feeRecipient',
'nonce',
'tokenID',
'chainType',
'chainID',
'data',
'version'
] as const
return keys.map(key => `${key}:${everpayTxWithoutSig[key]}`).join('\n')
}
Where EverpayTxWithoutSig
can be found in everpay-js types#EverpayTxWithoutSig
Example of an ethereum account
const messageData = `tokenSymbol:usdt
action:transfer
from:0x26361130d5d6E798E9319114643AF8c868412859
to:5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo
amount:5260000
fee:0
feeRecipient:0x6451eB7f668de69Fb4C943Db72bCF2A73DeeC6B1
nonce:1626079771946
tokenID:0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee
chainType:ethereum
chainID:42
data:{"hello":"world","this":"is everpay"}
version:v1`
Example of an arweave account
const messageData = `tokenSymbol:TUSDC
action:transfer
from:5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo
to:0x26361130d5d6E798E9319114643AF8c868412859
amount:1000000
fee:0
feeRecipient:0xfAC49e12F19743FFc3A756294f1bf70C282E25fA
nonce:1708507073627
tokenID:0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712
chainType:bsc
chainID:97
data:
version:v1`
Example of a smart account
const messageData = `tokenSymbol:TUSDC
action:transfer
from:eidd92c8451f8c5f1e4ab05ad75bfee0acfd5bbe5e3cf2f99e1fad5d4329fb650bc696b
to:0x26361130d5d6E798E9319114643AF8c868412859
amount:100000
fee:0
feeRecipient:0xfAC49e12F19743FFc3A756294f1bf70C282E25fA
nonce:1708507959655
tokenID:0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712
chainType:bsc
chainID:97
data:
version:v1`
everHash
Each everPay transaction has a uniquely identified everHash
. The personalMessageHash
generated by messageData
using the Ethereum hashPersonalMessage
, which is everHash
.
Generation rules
// cp from: https://github.com/ethereumjs/ethereumjs-util/blob/ebf40a0fba8b00ba9acae58405bca4415e383a0d/src/signature.ts#L168
const hashPersonalMessage = (message: Buffer): Buffer => {
const prefix = Buffer.from(
`\u0019Ethereum Signed Message:\n${message.length.toString()}`,
'utf-8'
)
return keccak256(Buffer.concat([prefix, message]))
}
const getPersonalMessageHash = (messageData: string): string => {
const personalMsgBuf = hashPersonalMessage(Buffer.from(messageData))
const personalMessageHash = `0x${personalMsgBuf.toString('hex')}`
return personalMessageHash
}
signature
Every everPay transaction requires a signature via the wallet of the sender's account or the webauthn biometrics of the smart account, and the everPay server verifies the validity of all signatures.
Ethereum Account Model
Get signature
by signing messageData
with Ethereum personalSign
.
Generate signature with everPay Tx via ethers.js
const everpayTxWithoutSig = {
tokenSymbol: 'usdt',
action: 'transfer',
from: '0x26361130d5d6E798E9319114643AF8c868412859',
to: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo',
amount: '5260000',
fee: '0',
feeRecipient: '0x6451eB7f668de69Fb4C943Db72bCF2A73DeeC6B1',
nonce: '1626079771946',
tokenID: '0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee',
chainType: 'ethereum',
chainID: '42',
data: '{"hello":"world","this":"is everpay"}',
version: 'v1'
}
// const messageData = getEverpayTxMessageData(everpayTxWithoutSig)
const messageData = `tokenSymbol:usdt
action:transfer
from:0x26361130d5d6E798E9319114643AF8c868412859
to:5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo
amount:5260000
fee:0
feeRecipient:0x6451eB7f668de69Fb4C943Db72bCF2A73DeeC6B1
nonce:1626079771946
tokenID:0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee
chainType:ethereum
chainID:42
data:{"hello":"world","this":"is everpay"}
version:v1`
// personalSign
const signMessageAsync = async (ethConnectedSigner: Signer, message: string): Promise<string> => {
return await ethConnectedSigner.signMessage(message)
}
const signature = await signMessageAsync(ethConnectedSigner, messageData)
Pseudocode reference source: everpay-js src/lib/sign.ts.
Arweave Account Model
A personalMessageHash
generated by messageData
using the Ethereum hashPersonalMessage
. And the Uint8Array
(or Buffer
) corresponding to personalMessageHash
is signed by arweave RSA-PSS sha256, and the signature result is then base64
converted by Arweave.utils.bufferTob64Url
(which differs from other base64 conversion functions), after conversion, and splice with ,{{arOwner}}
, we get signature
.
Generate signature with everPay Tx via arweave.js
const everpayTxWithoutSig = {
"tokenSymbol": "TUSDC",
"action": "transfer",
"from": "5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo",
"to": "0x26361130d5d6E798E9319114643AF8c868412859",
"amount": "1000000",
"fee": "0",
"feeRecipient": "0xfAC49e12F19743FFc3A756294f1bf70C282E25fA",
"nonce": "1708507073627",
"tokenID": "0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712",
"chainType": "bsc",
"chainID": "97",
"data": "",
"version": "v1"
}
// const messageData = getEverpayTxMessageData(everpayTxWithoutSig)
const messageData = `tokenSymbol:TUSDC
action:transfer
from:5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo
to:0x26361130d5d6E798E9319114643AF8c868412859
amount:1000000
fee:0
feeRecipient:0xfAC49e12F19743FFc3A756294f1bf70C282E25fA
nonce:1708507073627
tokenID:0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712
chainType:bsc
chainID:97
data:
version:v1`
const signMessageAsync = async (arJWK: ArJWK, messageData: string): Promise<string> => {
const arweave = Arweave.init(options)
const msgDataBuffer = Buffer.from(messageData, 'utf-8')
let arOwner = ''
let signatureB64url = ''
// web
if (arJWK === 'use_wallet') {
try {
await checkArPermissions('ACCESS_PUBLIC_KEY')
} catch {
throw new Error(ERRORS.ACCESS_PUBLIC_KEY_PERMISSION_NEEDED)
}
try {
arOwner = await (window.arweaveWallet).getActivePublicKey()
} catch {
throw new Error(ERRORS.ACCESS_PUBLIC_KEY_FAILED)
}
try {
await checkArPermissions('SIGNATURE')
} catch {
throw new Error(ERRORS.SIGNATURE_PERMISSION_NEEDED)
}
const algorithm = {
name: 'RSA-PSS',
saltLength: 32
}
if ((window.arweaveWallet as any).signMessage !== undefined) {
try {
const signature = await (window.arweaveWallet as any).signMessage(
msgDataBuffer,
{ hashAlgorithm: 'SHA-256' }
)
const buf = new Uint8Array(Object.values(signature))
signatureB64url = Arweave.utils.bufferTob64Url(buf)
} catch {
throw new Error(ERRORS.SIGNATURE_FAILED)
}
} else {
try {
const hash = sha256(messageData)
const signature = await (window.arweaveWallet).signature(
hexToUint8Array(hash.toString()),
algorithm
)
const buf = new Uint8Array(Object.values(signature))
signatureB64url = Arweave.utils.bufferTob64Url(buf)
} catch {
throw new Error(ERRORS.SIGNATURE_FAILED)
}
}
// node
} else {
const hash = sha256(messageData)
const buf = await arweave.crypto.sign(arJWK, hexToUint8Array(hash.toString()), {
saltLength: 32
})
arOwner = arJWK.n
signatureB64url = Arweave.utils.bufferTob64Url(buf)
}
return `${signatureB64url},${arOwner}`
}
const signature = await signMessageAsync(config.arJWK as ArJWK, messageData)
Pseudocode reference source: everpay-js src/lib/sign.ts
Smart Account Model
const everpayTxWithoutSig = {
"tokenSymbol": "TUSDC",
"action": "transfer",
"from": "eidd92c8451f8c5f1e4ab05ad75bfee0acfd5bbe5e3cf2f99e1fad5d4329fb650bc696b",
"to": "0x26361130d5d6E798E9319114643AF8c868412859",
"amount": "100000",
"fee": "0",
"feeRecipient": "0xfAC49e12F19743FFc3A756294f1bf70C282E25fA",
"nonce": "1708507959655",
"tokenID": "0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712",
"chainType": "bsc",
"chainID": "97",
"data": "",
"version": "v1"
}
// const messageData = getEverpayTxMessageData(everpayTxWithoutSig)
const messageData = `tokenSymbol:TUSDC
action:transfer
from:eidd92c8451f8c5f1e4ab05ad75bfee0acfd5bbe5e3cf2f99e1fad5d4329fb650bc696b
to:0x26361130d5d6E798E9319114643AF8c868412859
amount:100000
fee:0
feeRecipient:0xfAC49e12F19743FFc3A756294f1bf70C282E25fA
nonce:1708507959655
tokenID:0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712
chainType:bsc
chainID:97
data:
version:v1`
const hashPersonalMessage = (message: Buffer): Buffer => {
const prefix = Buffer.from(
`\u0019Ethereum Signed Message:\n${message.length.toString()}`,
'utf-8'
)
return keccak256(Buffer.concat([prefix, message]))
}
const getPersonalMessageHash = (messageData: string): string => {
const personalMsgBuf = hashPersonalMessage(Buffer.from(messageData))
const personalMessageHash = `0x${personalMsgBuf.toString('hex')}`
return personalMessageHash
}
const signMessageAsync = async (debug: boolean, isSmartAccount: boolean, email: string, everHash: string, accountData?: any): Promise<string> => {
if (accountData == null) {
const everpayHost = getEverpayHost(debug)
const everId = genEverId(email)
accountData = await getAccountData(everpayHost, everId)
}
const arr = Object.entries(accountData.publicValues) as any
const publicKeyData = {
allowCredentials: arr.map((publicIdValueArr: any) => {
const id = publicIdValueArr[0]
return {
type: 'public-key',
id: Arweave.utils.b64UrlToBuffer(id),
transports: [
'internal',
'usb',
'nfc',
'ble'
].concat(!isMobile ? ['hybrid'] : [])
}
})
}
const assertion = await navigator.credentials.get({
publicKey: {
...publicKeyData,
timeout: 300000,
userVerification: 'required',
challenge: Arweave.utils.b64UrlToBuffer(window.btoa(everHash)),
rpId: getRpId()
} as any
}) as any
if (assertion === null) {
throw new Error('cancelled')
}
const authenticatorData = assertion.response.authenticatorData
const clientDataJSON = assertion.response.clientDataJSON
const rawId = assertion.rawId
const signature = assertion.response.signature
const userHandle = assertion.response.userHandle
const sigJson = {
id: assertion?.id,
rawId: Arweave.utils.bufferTob64Url(rawId),
clientDataJSON: Arweave.utils.bufferTob64Url(clientDataJSON),
authenticatorData: Arweave.utils.bufferTob64Url(authenticatorData),
signature: Arweave.utils.bufferTob64Url(signature),
userHandle: Arweave.utils.bufferTob64Url(userHandle)
}
const sig = window.btoa(JSON.stringify(sigJson))
return `${sig},${accountData.publicValues[assertion?.id]},FIDO2`
}
const signature = await signMessageAsync(true, true, '[email protected]', getPersonalMessageHash(messageData))
- For ethereum
personalSign
signature ismessageData
string, and the result is thesignature
; - For arweave RSA-PSS sha256 signature is
messageDataHash
Buffer, the result needs to be further converted byArweave.utils.bufferTob64Url
to getbase64 string
and spliced with,{{arOwner}}
, which issignature
. - Signature for smart account webauthn
- use
getPersonalMessageHash
to geteverHash
frommessageData
. - get the public key information of the eid corresponding to the email account through the backend interface.
- use the corresponding public key information to call webauthn to sign the
everHash
. - Assemble the final signature result
- use
Signature Checksum
Every everPay transaction needs to be signed by the wallet of the sender's account or the webauthn biometrics of the smart account, and then the signed everPay transaction is submitted to the everPay server, which verifies all the signatures to ensure the validity of the transaction.
Ethereum Account Model
const signature = await signMessageAsync(ethConnectedSigner, messageData)
const verified = ethers.utils.verifyMessage(messageData, signature).toLowerCase() === everpayTxWithoutSig.from.toLowerCase()
Arweave Account Model
const signature = await signMessageAsync(config.arJWK as ArJWK, message)
const hash = sha256(messageData)
// arOwner is the publicKey of the arweave wallet
const [sigB64url, arOwner] = signature.split(',')
const verified = arweave.crypto.verify(
arOwner,
hexToUint8Array(hash.toString()),
Arweave.utils.b64UrlToBuffer(sigB64url)
)
Smart Account Model
Reference: verifyMessage.test.ts#L45
Submit a transaction
Submit everPay transaction to everPay backend server via POST request to tx
interface.
Field Descriptions
In addition to the Schema
definition field, supplemental signatures get signature
as the sig
field.
supplementary fields | description |
---|---|
sig | Generated signature , based on different account model signatures. |
Example
{
tokenSymbol: 'ar',
action: 'transfer',
from: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo',
to: '0x26361130d5d6E798E9319114643AF8c868412859',
amount: '100',
fee: '0',
feeRecipient: '0x6451eB7f668de69Fb4C943Db72bCF2A73DeeC6B1',
nonce: '1629276767583',
tokenID: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0xcc9141efa8c20c7df0778748255b1487957811be',
chainType: 'arweave,ethereum',
chainID: '0,42',
data: '{"hello":"world","this":"is everpay"}',
version: 'v1',
sig: 'Lg8Xgk_LZn_H-HVOz042wbhv5RVQjc7Z0iVV4_UbWWgoqHnboB6PQujtCtu1_QW0cXPqakm9sLi7fJlhK7Hm7UMFQiwXbVB_bClr73GKAcV0tpWye9BUsKw9SfOnFAHCCufF4C1PPt4xRrJp5UeG-smonQ9k4t0GmoXnXoSfmFxsEvaId5SeNaOZa1JYMzReo8-P4m5EdrTKLNgWwo28OOi4GbpXIzRxorJp-dwhsNhQHu4vzOq4rflGRwQKb9bj4S92YqEp2wXRRU7ebEiBJlGjQrf0HgTr7gZO_q3gI5FQgsL_UbOo4sp5hL69IUOfRxmr_RTiLZZzQRu-0dJBsWOSWYC5232fRf3MwogIELdDUl3dVCz5PDnXp8AOPKBQCiblu74oTSyKhsVMvwfER125dXyKtxJLlxTkDhEOPzTJdufy-Czs1pE_ZPKj4z44P3W7UdGiAt9rXYQb6JjMNOpG1_S7RMa5OKoCV4MbuK2CGFCNBE0h4zxeXZfXLOMSImrfFZ4nZAHkTbpKgpH1hPDEMGsEEgwvDl6_AyjrMOebAAyJGj6keyy9tf7lQBKKaj2-bGG6PIVC-l7wCXJizFt-3Cb0aC6ZXaCHGdhHuI-7Ime4M5iZESiBhkIhtOV3ADjqks174o0J0zKxE0NVl14tuu5tY-UfJ6kAkqbujLs,odtNk97a4PARR0I8g3kQpzlFVmPg-udyjfl81fbTioyP2pEw5tP5A1-FVqR-QFFPskW-j7yAze5usYNWHEir7oVQ9d9bbkcZIDEPqwSTO1JoD1BKXeeBK0xsmiSgxeY7uuRXWdhXREhlmIMsV8ObakEeXdbbxbs89XaZHBuES7boASrRVDXRz_mhMu6u_58OdLeMwR3I1BCH6nphNGVOehA7GOOqEBvtesBset0bNaLCb0JpSg5ZW_0AGLP-XydzE3IPLLx4NQEEJY21y8fChxYM4jntI78l5hojp9NlmS69EXlj0PoMjsbaWaz9WtnZaMAbnaOGAHhv8Y_TNmBI0FHpqHaGPP906Mnrgdm3tl2L40EX-Q6-liNVkB56CmPxXzSesu-4x5LLYxQ-aX3W6Hj7RCDTacxqUJHzOrhJqXSx6Jx0t8CwyfReMgVv4p5t1C3OZ8yYbJ_H3LdkeriVniaC5jQdMyIJ6QBMzr1XdXIw9WuEG2kCIYtvOp2qDuu9o2SY-9W4Yv7VWRDfWO38xxR4ZO65MMAdZxeaZ4w8sK_owH46Wm0XoT3Al-LPypaeijWqlHEu4R8c2ersD3xkDvXC_lNtaQw_qyfI3UEH5fWupY4zhZeDGkvXQh32Fv4CxlZL58iUHv9SvR7p5LgBCC3AVUbn7Sqc4xPUCZMj-Tc'
}
nonce
nonce
is generated by the user on the clients' side and can be received by everPay's server 100s up or down from the server time.- every time a user submits an everPay transaction,the
nonce
must be larger than the user's lastnonce
value.
Transaction Record
The everPay transaction is submitted to everPay's server and is passed by a signature verification that can get the transaction record through everPay's interface.
everPay adds some fields to the Schema
definition field and sig
signature field for everPay's service.
Field Descriptions
supplementary fields | description |
---|---|
everHash | each everPay transaction corresponds to a unique everHash , everHash is generated with reference to everHash. |
timestamp |
|
status |
|
internalStatus | The field returns a specific error message only if internal transfers fails in the bundle transaction. The value is success for successful internal transfers, withdrawals and recharges. |
id |
|
targetChainTxHash |
|
Query Interface
- txs Checks all everPay transaction records.
- txsByAccount Checks the everPay transaction records of a specific everPay account.
- txByHash Checks the everPay transaction record based on
everHash
. - mintedTxByChainTxHash Checks the everPay transaction record of the deposit according to the blockchain record ID. (e.g.
txHash
for ethereum)