Skip to main content

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

FieldDescription
tokenSymbolToken Symbol, AR,ETH,USDT,USDC etc.
action
  • 'mint' to deposit.
  • 'transfer' to transfer.
  • 'burn' to withdraw.
  • 'bundle' to batch execute internal transfers.
fromthe current everPay account ID that signed the transaction.
to
  • When transferring, to is another everPay account ID.
  • When withdrawing, to is the blockchain wallet address to withdraw to.
  • When using bundle transaction, to represents the everPay account ID of the external transfer recipient, which can be any everPay account ID. (including the current everPay account ID of the signed transaction)
amountType uint; decimals processing is required for setting, e.g. 0.1USDT, after USDT's decimals: 6 processing, it 100000.
  • When transferring, amount is the transfer amount.
  • When withdrawing, amount is the withdrawal amount.
  • When using bundle transaction, amount is the external transfer amount.
feeHandling fee, type uint. needs to be decimals, e.g. 0.1USDT, here it's 100000 after USDT's decimals: 6 processing.
feeRecipientReceive everPay account ID for handling fees, via info API interface to get.
nonceunix milliseconds.
tokenIDvia info API interface, must be consistent with the token id field corresponding to tokenSymbol.
chainTypechainType must be the same as info API, the token chainType consistent.
chainIDchainID must be the same as info API, the token chainID consistent.
dataAdditional information, developer-customizable JSON data, processed by JSON.stringify() and passed in. Developers can pass data to customize some complex functions, like Bundle.
versiontransaction 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))
danger
  • For ethereum personalSign signature is messageData string, and the result is the signature;
  • For arweave RSA-PSS sha256 signature is messageDataHash Buffer, the result needs to be further converted by Arweave.utils.bufferTob64Url to get base64 string and spliced with ,{{arOwner}}, which is signature.
  • Signature for smart account webauthn
    1. use getPersonalMessageHash to get everHash from messageData.
    2. get the public key information of the eid corresponding to the email account through the backend interface.
    3. use the corresponding public key information to call webauthn to sign the everHash.
    4. Assemble the final signature result

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 fieldsdescription
sigGenerated 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 last nonce 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 fieldsdescription
everHasheach everPay transaction corresponds to a unique everHash, everHash is generated with reference to everHash.
timestamp
  • When the everPay transaction is recorded on the Arweave blockchain, this timestamp represents the unix milliseconds of the everPay transaction being recorded on the Arweave blockchain.
  • If the everPay transaction is not recorded on the Arweave blockchain, timestamp is 0.
status
  • confirmed means the everPay transaction has been confirmed by everPay's backend signature verification, but not yet recorded on the Arweave blockchain.
  • packaged means the everPay transaction has been recorded on the Arweave blockchain.
internalStatusThe 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
  • When the everPay transaction is recorded on the Arweave blockchain, the id corresponds to the hash of the transaction recorded on Arweave.
  • If the everPay transaction is not recorded on the Arweave blockchain, the id is an empty string.
targetChainTxHash
  • The corresponding blockchain txHash for deposits and withdrawals.
  • If the withdrawal is not completed or is an everPay transfer transaction, this targetChainTxHash will be an empty string.

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)