Building your own Venmo with Stellar
Stellar is a distributed ledger technology which allows anyone to build low-cost and fast financial services. This tutorial will walk you through some of its features and show you how to create a Venmo clone on top of Stellar called AnchorX
.
In order to maintain customer accounts, Stellar requires you to create some sort of organization. Think about it as a "Stellar company" but in Stellar jargon, that is called an anchor
. There are 2 ways to do this. Either you create Stellar accounts on behalf of customers or use the memo field of the transaction to operate on behalf of your customers/users.
The official documentation covers the second method but there is no documentation about the first one.
This tutorial will show you how to create an anchor maintaining a Stellar account for each customer, hiding the implementation details. It will use the programming language JavaScript and the mobile wallet will be written using React Native.
Concepts
Stellar Consensus Protocol
Stellar is built on top of the Stellar Consensus Protocol (SCP), which defines a way to reach consensus without relying on close systems, this is what helps maintaining the Stellar ledger. Explaining how the protocol works is beyond the scope of this tutorial, but you can learn more about here
Stellar Development Foundation
The Stellar Development Foundation (SDF) is a non-profit corporation which develops and maintains the Stellar Network and the Stellar Protocol. You can learn more about them in the mandate page
Stellar Core and Horizon API
The Stellar Core
software is in charge of communicating and maintaining the Stellar network. Using the Stellar Consensus Protocol (SCP), it does validation and agrees on the status of transactions in the network. Anyone can run the software. Running Stellar Core is similar to running an Ethereum or Bitcoin node, except that there is no mining involved.
Horizon
is a RESTful API which allows you to interact with Stellar Core
and makes it easy to build applications using the network. With it you can submit transactions, read accounts or subscribe to events in the network.
You can interact with it using any HTTP client like cURL, Postman or Paw. There are also SDKs for different programming languages like Go, Java, JavaScript, Ruby, etc.
Setting up Stellar Core
or Horizon
won't be included here but you can learn about it in the official guides.
For this tutorial you will be using a test network (testnet) run by the SDF under https://horizon-testnet.stellar.org/ and the JavaScript Stellar SDK.
Account
The most important unit in Stellar are the accounts. You need to create one before interacting with the network. To create an account you need to deposit Lumens into it, you can get the Lumens buying them in a exchange or asking a friend for some.
When you create an account, you get a public and private key. The public key is the equivalent of your bank account number and then the private key is the password. The private key is required to sign each transaction.
Creating accounts in the test network
For now, run the code below on repl.it https://repl.it/@abuiles/CreateStellarAccount
const StellarSdk = require('stellar-sdk')
const fetch = require('node-fetch')
// Generates a keypair and funds account with friendbot
async function createAccount() {
const pair = StellarSdk.Keypair.random()
console.log('Requesting Lumens')
await fetch(`https://horizon-testnet.stellar.org/friendbot?addr=${pair.publicKey()}`)
return pair
}
async function run() {
const pair = await createAccount()
console.log(`
Congrats, you have a Stellar account in the test network!
seed: ${pair.secret()}
id: ${pair.publicKey()}
`)
const url = `https://horizon-testnet.stellar.org/accounts/${pair.publicKey()}`
console.log(`
Loading account from test network:
${url}
`)
const response = await fetch(url)
const payload = await response.json()
}
run()
/* Example output
Congrats, you have a Stellar account in the test network!
seed: SCUNXTHOURNM4W4HIUQ7GZIQ25VHD4UPRWWCCX6LY24EZXMGGNVBJVST
id: GBWR2HBRWPINWB5VEAXXV5QUHRCT7HC7P635UCZPX3MGGCGQRIKDL4R6
Loading account from test network:
https://horizon-testnet.stellar.org/accounts/GBWR2HBRWPINWB5VEAXXV5QUHRCT7HC7P635UCZPX3MGGCGQRIKDL4R6
*/
On the right you can see how to generate a keypair
with the Stellar JS SDK
and
then ask a service run by the Stellar Development Foundation called friendbot
to
give us some initial Lumens.
Assets
// https://horizon.stellar.org/accounts/GBCFAMVYPJTXHVWRFP7VO6F4QE7B4UHAVJOEG5VR6VEB5M67GHQGEEAB
{
"id": "GBCFAMVYPJTXHVWRFP7VO6F4QE7B4UHAVJOEG5VR6VEB5M67GHQGEEAB",
"account_id": "GBCFAMVYPJTXHVWRFP7VO6F4QE7B4UHAVJOEG5VR6VEB5M67GHQGEEAB",
"balances": [
{
"balance": "0.0000000",
"limit": "922337203685.4775807",
"asset_type": "credit_alphanum4",
"asset_code": "EURT",
"asset_issuer": "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S"
},
{
"balance": "0.0000000",
"limit": "922337203685.4775807",
"asset_type": "credit_alphanum4",
"asset_code": "ETH",
"asset_issuer": "GBDEVU63Y6NTHJQQZIKVTC23NWLQVP3WJ2RI2OTSJTNYOIGICST6DUXR"
},
{
"balance": "0.0000000",
"limit": "922337203685.4775807",
"asset_type": "credit_alphanum4",
"asset_code": "USD",
"asset_issuer": "GBSTRH4QOTWNSVA6E4HFERETX4ZLSR3CIUBLK7AXYII277PFJC4BBYOG"
},
{
"balance": "57.9899400",
"asset_type": "native"
}
]
}
Assets are resources with economic value like money, equity, real estate, goats, you name it. Cryptocurrencies allow you to represent any kind of asset. Stellar is no exception, it allows any account to define traditional assets or even come up with new exciting assets. These can be easily traded and exchanged over the Stellar network.
Under the blankets, Stellar uses a native asset which is called the Lumen represented with the symbol XLM.
Assets still feel a bit abstract. On the right you can find a concrete example of how an account balance could look like in JSON format.
It contains the following assets:
- EURT: Asset issued by Tempo a remittances company.
- ETH: This asset represents Ether, you send real
ETH
to https://apay.io/ and they credit your Stellar account with theirETH
. - USD: Asset representing
Dollars
, issued by Stronghold. - native: Native asset of the network, it represents
Lumens
.
You can learn more about assets in the SDF guides: https://www.stellar.org/developers/guides/concepts/assets.html
Anchor
At the beginning of this tutorial, you read that an entity like Venmo is called an anchor. Anchors issue assets on top of Stellar and then credit those asset to other Stellar accounts. If the anchor represents fiat, then it is likely an authorized entity to deal with money like banks, savings and credit institutions or a remittance company. User deposit fiat to the anchor's account and they credit the user Stellar account with the equivalent balance.
This is how banks or Venmo works but instead of using a public ledger like Stellar, they have their own private system and use third parties like ACH or SWIFT to move money around.
Anchors can represent also other cryptocurrencies. Papaya is an anchor which includes support for cryptocurrencies like ETH
, BTC
and others.
If you want to deposit Ether, they give you an Ethereum address. After you deposit Ether to that address they issue the equivalent to your Stellar account. In this case you'll have to trust Papaya because they'll be acting as custodian for the Ether you sent them.
In this tutorial, you will be creating an anchor which will issue an asset representing USD. User will have to download an app, you'll fake a KYC process and then create a Stellar account for the user and authorize the user to hold the asset. Once the user have been authorized they will be able to deposit USD or debit USD from their accounts.
You can learn more about anchors in the SDF guides: https://www.stellar.org/developers/guides/anchor/
Mapping Venmo to Stellar
The following is high level overview of what happens when you want to use Venmo:
- Download the app and create an user.
- Go through phone number, email and bank account verification.
- Once you are authorized to use Venmo, transfer money from your bank account and send it to other Venmo users.
- Transfer to your bank whatever balance you have left.
Let's translate the steps above to actions in AnchorX and then identify the requirements to setup the anchor.
Download the app and create an user
Users should be able to download an app and then create an account. To keep things the application will use username (no password) for sign-up/sign-in.
Account verification
In Venmo there is a verification process, since this is a toy example you won't be including that. By default every user will be marked as verified. In real application, you probably want to collect user data like SSN, driver's license, passport, proof of residence, etc.
Credit from bank account
The app will have a section which will simulate transferring from your bank account.
P2P payments
Once the account has been provisioned and have some Dollars, users should be able to send money to other users.
Transfer balance from AnchorX to Bank account
The app will have a section for depositing their dollars to their bank account.
Building the backend
In this section, you'll be implementing a GraphQL API which will support user sign-up and sign-in, deposits, withdrawals and payments. The mobile application will be interacting with this API.
The server will be written in TypeScript
and use Prisma to generate the user management API.
Setting up the server
In this section you can find a GraphQL server created with GraphQL CLI and using the TypeScript
template. To make things easy there is boilerplate project which you can use to get started.
Download the boilerplate running the following commands:
git clone https://github.com/abuiles/anchorx-api-boilerplate anchorx-api
cd anchorx-api
Next you are going to add the user model.
User model
The user model is defined in database/datamodel.graphql
. Users in AnchorX signup using their username. After they signup, the service assigns automatically a Stellar account. The code on the right is the prisma representaion for the user model.
type User {
id: ID! @unique
username: String! @unique
stellarAccount: String!
stellarSeed: String!
}
After adding the user model definition to database/datamodel.graphql
, you have to run yarn prisma deploy
to create a new table in the database and get the CRUD end-points for users.
The pull request #2, shows the changes added in this step. The important changes are https://github.com/abuiles/anchorx-api/pull/2/files#diff-5ca8cc3ddf0d92dda0872ee778220e21, the rest is code generated by prisma.
Next you need to add a GraphQL mutation to support user signup. After a new user is created, you need to provision a Stellar account. Provisioning a new account requires multiple steps:
- Create a public and private key pair.
- After the public key has been created, fund that account with some Lumens.
- After the account has been created in the Stellar ledger, create a trustline with the anchor asset.
User signup mutation
Edit src/schema.graphql to define the schema
# import User from './generated/prisma.graphql'
type Query {
user(username: String!): User
}
type Mutation {
signup(username: String!): User!
}
Define the resolvers and query in
src/index.ts
. Commit 5d6e91 shows the changes in theMutation
and commit 3d3fff shows the changes in theQuery
const resolvers = {
Query: {
user(_, { username }, context: Context, info) {
return context.db.query.user(
{
where: {
username
}
},
info
)
}
},
Mutation: {
signup(_, { username }, context: Context, info) {
const data = {
username,
stellarAccount: '1234',
stellarSeed: '1234'
}
return context.db.mutation.createUser(
{ data },
info
)
},
},
}
In this section you will start defining the GraphQL schema and define the resolvers.
Start by defining a mutation called signup
and a query called user
. The first one takes a username and returns an User
instance and the second one allows you to retrieve an user given their username.
Next you need to add the code in the resolvers to support signup and user find, open the file src/index.ts
and make sure that it looks like the code on the right. There are links to diffs included so you can see exactly what changed. For this tutorial Prisma is taking care of the database and ORM setup.
In the mutation signup, you'll notice that stellarAccount
and stellarSeed
have a fixed value of 1234
, that's just a placeholder. In the next section will add the JS Stellar SDK
and use it to generate the account and the seed, also we'll add a fake service to encrypt the seed before saving it to the database.
You can see all the changes in this section in pull request #3
Assigning a Stellar account to new users
Install the stellar-sdk and then the TypeScript types for the stellar-sdk
yarn add stellar-sdk
yarn add @types/stellar-sdk
When a user signup the service will generate a Stellar account. First you need to install the JS Stellar SDK
and the types definitions for TypeScript
.
After installing the SDK, you need to change the signup mutation so it generates a real public key and seed. To do so, the Stellar SDK
has a class called Keypair. You'll need to import Keypair and then call Keypair.random()
to generate a new pair.
On the right you can see the changes to src/index.ts
.
import { importSchema } from 'graphql-import'
import { Prisma } from './generated/prisma'
import { Context } from './utils'
+import { Keypair } from 'stellar-sdk'
const resolvers = {
Query: {
...
},
Mutation: {
signup(_, { username }, context: Context, info) {
+ const keypair = Keypair.random()
+
const data = {
username,
+ stellarAccount: keypair.publicKey(),
+ stellarSeed: keypair.secret()
}
...
You should never store seed keys as plain text. In a production app you should probably be using something like Google or AWS KMS and have a clear set of restrictions of who can encrypt or decrypt. In this tutorial you'll be using an encryption module for Node called crypto-js
. In the next section you will add the module and encrypt the seed before saving it.
Encrypting the seed
Add the crypto-js module with its types.
yarn add crypto-js
yarn add @types/crypto-js
Use crypto-js before storing the seed key
import { Prisma } from './generated/prisma'
import { Context } from './utils'
import { Keypair } from 'stellar-sdk'
+import { AES } from 'crypto-js'
const resolvers = {
Query: {
signup(_, { username }, context: Context, info) {
const keypair = Keypair.random()
+ const configCryptoScret = 'StellarIsAwesome-But-Do-Not-Put-This-Value-In-Code'
+
+ const secret = AES.encrypt(
+ keypair.secret(),
+ configCryptoScret
+ ).toString()
+
const data = {
username,
stellarAccount: keypair.publicKey(),
- stellarSeed: keypair.secret()
+ stellarSeed: secret
}
...
Install crypto-js
and after that you can use it in the signup
mutation to store the encrypted seed.
You can see the changes from the previous two sections in pull request #4
Testing
mutation {
signup(username: "test") {
id,
username,
stellarSeed,
stellarAccount
}
}
You can test the changes made up to this point by running the server. To do so type the command yarn dev
in your console and it will bring a GraphQL playground to your browser. You can test the signup mutation
by pasting the code in the right and hitting run.
AnchorX allows you now to create new users and it assigns a stellar account to each user. However, the Stellar account is not created in the Stellar ledger until it gets funded with some lumens.
The GIF below, shows you the process of creating an account and what happens when you try to look at that account on the stellar test network.
The result is a 404 since you never funded the Stellar account.
To fix this, AnchorX needs to fund each account with enough lumens to
keep the minimum account balance and then be able to do
transactions. In the next section you will learn how to create account
programatically in Stellar without friendbot
.
Creating the account in the Stellar ledger
// Classes required to create new account
import {
Keypair, // Keypair represents public and secret keys.
Network, // Network provides helper methods to get the passphrase or id for different stellar networks.
Operation, // Operation helps you represent/build operations in Stellar network.
Server, // Server handles the network connections.
TransactionBuilder // Helps you construct transactions.
} from 'stellar-sdk'
try {
// Tell the Stellar SDK you are using the testnet
Network.useTestNetwork();
// point to testnet host
const stellarServer = new Server('https://horizon-testnet.stellar.org');
// Never put values like the an account seed in code.
const provisionerKeyPair = Keypair.fromSecret('SA72TGXRHE26WC5G5MTNURFUFBHZHTIQKF5AQWRXJMJGZUF4XY6HFWJ4')
// Load account from Stellar
const provisioner = await stellarServer.loadAccount(provisionerKeyPair.publicKey())
console.log('creating account in ledger', keypair.publicKey())
const transaction = new TransactionBuilder(provisioner)
.addOperation(
// Operation to create new accounts
Operation.createAccount({
destination: keypair.publicKey(),
startingBalance: '2'
})
).build()
// Sign the transaction above
transaction.sign(provisionerKeyPair)
// Submit transaction to the server
const result = await stellarServer.submitTransaction(transaction);
console.log('Account created: ', result)
} catch (e) {
console.log('Stellar account not created.', e)
}
The Stellar SDK includes a transaction builder which helps you create operations. To create an account, you'll need to use the createAccount operation. The code on the right includes the relevant pieces to create a new account programmatically.
You can follow along and read the comment on what each line represents. After that you'll need to use that code in the context of the signup mutation to create the account in the Stellar ledger.
You need to extend the signup mutation to fund the user's account after it has been created successfully.
You can find in pull request #5 the change in the mutation using the create account operation.
Payments
import {
+ Asset,
Keypair,
+ Memo
Network,
Operation,
Server,
TransactionBuilder
} from 'stellar-sdk'
import {
AES,
+ enc
} from 'crypto-js'xo
const ENVCryptoSecret = 'StellarIsAwesome-But-Do-Not-Put-This-Value-In-Code'
mutations: { ...
+ async payment(_, { amount, senderUsername, recipientUsername, memo }, context: Context, info) {
+ // Load users from database
+ const result = await context.db.query.users({
+ where: {
+ username_in: [senderUsername, recipientUsername]
+ }
+ })
+
+ const sender = result.find(u => u.username === senderUsername)
+ const recipient = result.find(u => u.username === recipientUsername)
+
+ Network.useTestNetwork();
+ const stellarServer = new Server('https://horizon-testnet.stellar.org');
+
+ // build the keypair required to sign the payment transaction
+ const signerKeys = Keypair.fromSecret(
+ // Use something like KMS in production
+ AES.decrypt(
+ recipient.stellarSeed,
+ ENVCryptoSecret
+ ).toString(enc.Utf8)
+ )
+
+ // Load Stellar account from ledger
+ const account = await stellarServer.loadAccount(sender.stellarAccount)
+
+ /*
+ Payments require an asset type, for now users will be sending
+ lumens. In the next chapter you'll create a custom asset
+ representing Dollars and use it.
+ */
+ const asset = Asset.native()
+
+ let transaction = new TransactionBuilder(account)
+ .addOperation(
+ Operation.payment({
+ destination: sender.stellarAccount,
+ asset,
+ amount
+ })
+ ).build()
+
+ transaction.sign(signerKeys)
+
+ try {
+ const { hash } = await stellarServer.submitTransaction(transaction)
+
+ return { id: hash }
+ } catch (e) {
+ console.log(`failure ${e}`)
+
+ throw e
+ }
+ }
},
}
In Venmo you can send payments using the recipient's phone number, email or username. In AnchorX users will be sending money to each other using their usernames. To keep things simple, you won't implement any kind of session management which means you'll have to tell the API explicitly who is the sender and the recipient. For production apps this is very BAD IDEA, but the goal here is not to build an user management or Authn/Authz system.
To send a payment in Stellar, you need the recipient's Stellar account and source account secret.
The code in the right shows the mutation to create new payments. It takes the sender and recipient username as parameters. It loads first the user's data from the database, decrypts the sender seed and then signs the transaction to submit a payment.
Payments are created using the payment payment
function from the
Operation
class. You can see it takes the destination, the
asset and the amount.
Since Stellar accounts can hold multiple assets you need to specify which of the assets held by an account you are trying to transfer. For now the operation is sending lumens. In AnchorX, users won't be sending lumens to each other but the custom asset representing USD.
In the upcoming sections you will:
- Create a custom asset representing Dollars.
- Learn how to allow accounts to hold that asset.
- Implement the
creditAccount
mutation which simulates transferring Dollars from the user bank account to AnchorX. - Change the payment mutation to send USD instead of lumens.
Pull request #7 includes all the changes introduced in this section.
Issuing AnchorX custom asset
In this section you'll learn the high level details of issuing a new asset and how to allow other accounts to hold your asset. Stellar's official documentation has a great guide about issuing assets so this tutorial won't repeat the same. You can read more about it here https://www.stellar.org/developers/guides/issuing-assets.html
To create an asset you'll need an asset code and an issuing account.
The asset code needs a short identifier, for national currencies you need to use an ISO 4217 code. AnchorX asset will use the code USD
.
The issuing account can emit new USD
, give it to other accounts and impose some controls around it. In this tutorial the issuing account will be GBX67BEOABQAELIP2XTC6JXHJPASKYCIQNS7WF6GWPSCBEAJEK74HK36
with seed SBYZ5NEJ34Y3FTKADVBO3Y76U6VLTREJSW4MXYCVMUBTL2K3V4Y644UX
.
Before an account can hold a given asset, it needs to explicitly create
an operation expressing that it trust the asset with code USD
created by the issuing account
GBX67BEOABQAELIP2XTC6JXHJPASKYCIQNS7WF6GWPSCBEAJEK74HK36
, such
operation is called creating a trustline
.
The issuer can set a condition where it needs to authorize accounts before they can hold its asset, this is useful if you only want people who are your clients or have gone through your KYC process to transact with your asset. In this scenario you need two trustlines one from the customer's account to your issuing account and then one from your issuing account to the customer's account.
Setting up issuing account
import {
AuthRequiredFlag,
AuthRevocableFlag,
Server,
Keypair,
Network,
Operation,
TransactionBuilder,
xdr
} from 'stellar-sdk'
async function setupIssuer() {
Network.useTestNetwork()
const stellarServer = new Server('https://horizon-testnet.stellar.org')
const issuerKeyPair = Keypair.fromSecret('SBYZ5NEJ34Y3FTKADVBO3Y76U6VLTREJSW4MXYCVMUBTL2K3V4Y644UX')
const issuingAccount = await stellarServer.loadAccount(issuerKeyPair.publicKey())
var transaction = new TransactionBuilder(issuingAccount)
.addOperation(
Operation.setOptions({
setFlags: AuthRevocableFlag | AuthRequiredFlag
}))
.build()
transaction.sign(issuerKeyPair)
await stellarServer.submitTransaction(transaction)
console.log('All set!')
}
setupIssuer()
The code on the right shows you the basic setup for an account before creating an asset. In it, you are setting the AuthRequiredFlag
which requires the trustline from the issuer to the holder and AuthRevocableFlag
which allows you to freeze the user's access to your asset. You can read more about both flags here. Since this is something which is done once, this is not included in the GitHub repo. You can try it in Repl.it
Next you need to extend the signup mutation to create both trustlines when an user creates a new account.
Creating a trustline
import {
Asset,
Keypair,
Network,
Operation,
Server,
TransactionBuilder
} from 'stellar-sdk'
export async function createTrustline(accountKeypair) {
Network.useTestNetwork();
const stellarServer = new Server('https://horizon-testnet.stellar.org');
try {
const account = await stellarServer.loadAccount(accountKeypair.publicKey())
const transaction = new TransactionBuilder(account)
.addOperation(
Operation.changeTrust({
asset: AnchorXUSD
}))
.build();
transaction.sign(accountKeypair)
const result = await stellarServer.submitTransaction(transaction)
console.log('trustline created from account to issuer and signers updated', result)
return result
} catch (e) {
console.log('create trustline failed.', e)
}
}
The code on the right shows you how to create a trustline from an account to the issuer account. You'll be using that function inside the signup mutation.
Now after you create a new user, you will also create a trustline from the new account to AnchorX asset. You are not done yet, although the account accepts AnchorX USD
, if you try to send USD
to that account it won't work because the account hasn't been authorized to hold the asset.
After a trustline is created, you'll see the custom asset in the account's balance.
The following GIF shows you an account's state after it gets created without the trustline to AnchorX asset. You can see that the balance key only shows native.
And the next one will show you what happens in the account after creating the trustline.
You can see the changes from this section in pull request #8.
Allow trustline
export async function allowTrust(trustor) {
Network.useTestNetwork();
const stellarServer = new Server('https://horizon-testnet.stellar.org');
try {
// Never store secrets in code! Use something like KMS and put
// this somewhere were few people can access it.
const issuingKeys = Keypair.fromSecret('SBYZ5NEJ34Y3FTKADVBO3Y76U6VLTREJSW4MXYCVMUBTL2K3V4Y644UX')
const issuingAccount = await stellarServer.loadAccount(issuingKeys.publicKey())
const transaction = new TransactionBuilder(issuingAccount)
.addOperation(
Operation.allowTrust({
trustor,
assetCode: AnchorXUSD.code,
authorize: true
})
)
.build();
transaction.sign(issuingKeys);
const result = await stellarServer.submitTransaction(transaction)
console.log('trust allowed', result)
return result
} catch (e) {
console.log('allow trust failed', e)
}
}
On the right you can see the allow trust operation in used. It receives the public key of the account that you want to authorize and the asset's code.
Now you can use that function after calling createTrustline
. Pull request #9 shows you how to use the allowTrust
function inside the signup mutation.
Welcome balance
export async function payment(signerKeys: Keypair, destination: string, amount: string) {
Network.useTestNetwork();
const stellarServer = new Server('https://horizon-testnet.stellar.org');
const account = await stellarServer.loadAccount(signerKeys.publicKey())
let transaction = new TransactionBuilder(account)
.addOperation(
Operation.payment({
destination,
asset: AnchorXUSD,
amount
})
).addMemo(Memo.text('https://goo.gl/6pDRPi'))
.build()
transaction.sign(signerKeys)
try {
const { hash } = await stellarServer.submitTransaction(transaction)
return { id: hash }
} catch (e) {
console.log(`failure ${e}`)
throw e
}
}
In theory, AnchorX and the Stellar accounts it creates are ready to
receive and send USD
. AnchorX's growth hackers have decided that the
best way to bring people to the platform is by giving each user a
welcome bonus of $10 USD.
You need to extend the signup mutation to send new users $10 USD. Add the function on the right to utils and then call it after the allow trustline operation.
The function takes the signing keys for the account to be debited, the destination account and the amount.
You can use the payment function inside the signup mutation.
The following GIF show you how after creating new accounts, they end up with $10 USD in their balance.
Pull request #10 includes all the changes introduced in this section. Now that AnchorX users can hold USD, let's replace the payment mutation to send USD and also give them a way to debit or credit their accounts.
Paying with USD
async payment(_, { amount, senderUsername, recipientUsername, memo }, context: Context, info) {
const result = await context.db.query.users({
where: {
username_in: [senderUsername, recipientUsername]
}
})
const sender = result.find(u => u.username === senderUsername)
const recipient = result.find(u => u.username === recipientUsername)
const signerKeys = Keypair.fromSecret(
// Use something like KMS in production
AES.decrypt(
sender.stellarSeed,
ENVCryptoSecret
).toString(enc.Utf8)
)
try {
const { hash } = await payment(
signerKeys,
recipient.stellarAccount,
amount
)
return { id: hash }
} catch (e) {
console.log(`failure ${e}`)
throw e
}
}
}
You have a payment mutation to allow people to send money between each other. Until now it was sending the native asset but since users can hold USD you need to replace it to send USD. To do so, you can use the payment function from the previous section.
The code on the right shows you the new version of the payment mutation, which uses the payment
function.
The following GIF show you the payment mutation in action.
[Pull request #11[(https://github.com/abuiles/anchorx-api/pull/11) shows you the changes introduced in this section.
Credit account
async credit(_, { amount, username }, context: Context, info) {
const user = await context.db.query.user({
where: {
username: username
}
})
try {
const { hash } = await payment(
// keypair for issuing account - no bueno
Keypair.fromSecret('SBYZ5NEJ34Y3FTKADVBO3Y76U6VLTREJSW4MXYCVMUBTL2K3V4Y644UX'),
user.stellarAccount,
amount
)
return { id: hash }
} catch (e) {
console.log(`failure ${e}`)
throw e
}
}
In this section you will implement a new mutation to credit an user's account with USD
. This mutation simulates a callback which would have been called if you were integrating your system with ACH or a similar system. In the context of the tutorial, this will be called when the user sends money from their bank account to AnchorX.
The mutation takes the username and the amount to be credited. Since you are using the issues keys, you can see that every time a new credit happens, the amount of USD
minted increases.
To see how much is in circulation for a given asset you can visit the horizon end-point for assets filtering by code and issuer. The URL for AnchorX USD
is the following https://horizon-testnet.stellar.org/assets?order=desc&asset_code=USD&asset_issuer=GBX67BEOABQAELIP2XTC6JXHJPASKYCIQNS7WF6GWPSCBEAJEK74HK36
The code in the right shows you the mutation and pull request #12 includes the change in the project.
The GIF below shows you the credit mutation in action and how the amount of USD
increases after you credit accounts.
Debit account
async debit(_, { amount, username }, context: Context, info) {
const user = await context.db.query.user({
where: {
username: username
}
})
const keypair = Keypair.fromSecret(
AES.decrypt(
user.stellarSeed,
ENVCryptoSecret
).toString(enc.Utf8)
)
// When you send back a custom asset to the issuing account, the
// asset you send back get destroyed
const issuingAccount = 'GBX67BEOABQAELIP2XTC6JXHJPASKYCIQNS7WF6GWPSCBEAJEK74HK36'
try {
const { hash } = await payment(
keypair,
issuingAccount,
amount
)
console.log(`account ${keypair.publicKey()} debited - now transfer real money to ${username} bank account`)
return { id: hash }
} catch (e) {
console.log(`failure ${e}`)
throw e
}
}
In this section you will implement a new mutation to debit money from an user's account. This mutation simulates a callback which would have been called if you were integrating sending money to user's bank account. In AnchorX, this will be use when the user wants to withdraw money from AnchorX.
When crediting account you saw that USD
was being minted. For debiting, you'll be doing the opposite process, taking USD
from an user's account and sending it back to the issuing account. In Stellar, when an asset is sent back to the issuing account, it gets destroyed.
The code on the right shows the debit mutation. It is very similar to the credit operation, but the difference is that the destination is the issuing account. Pull request #13 shows the changes in the mutation and schema.
The following GIF shows you the debit mutation and how assets get destroyed after sending them back.
Conclusion
Congratulations! You have now a very basic implementation of AnchorX API. In this chapter you learnt:
- How to create Stellar accounts programatically.
- How to issue new assets in Stellar.
- How to create trustlines in Stellar and authorize truslines.
- Signing transactions and making payments with Stellar.
- What happens after an issuing account issues more assets.
- What happens after an issuing account receives back issued assets.
In the next chapter you'll be building a mobile wallet in React Native to allow AnchorX customer to interact with their accounts. You'll also learn how to use the Stellar JS SDK to read accounts data and follow payments. After you build the wallet, you will learn about best practices like managing secret keys, using a base account along with the issuing account, how to setup multisignatutre schemas and security considerations.
Building the mobile wallet
In this chapter you'll learn how to use the Stellar JS SDK in React Native and use it to display balances and transactions. This tutorial won't go into details on things which are not related with Stellar like setting up React Native, navigation, setting Apollo, etc. This tutorial includes a boilerplate project which has all the basics already setup.
When interacting with Stellar, you will find a detailed section explaining what's happening.
The wallet will be written using the following technologies:
Mobile wallet boilerplate
The following GIF shows you all the screens and functionality included in the mobile wallet boilerplate.
As you can see the login/signup screen is already configured to consume the GraphQL
API. After the user signs in you will display the balance and transaction history and give the user the option to make new payments.
Before downloading the boilerplate, install the required dependencies to run React Native applications. You can find the list of requirements in the following page https://facebook.github.io/react-native/docs/getting-started, click in Build Projects with Native Code
and follow the instructions.
Next, download the boilerplate with the following commands:
git clone https://github.com/abuiles/StellarMobileWalletBoilerplate AnchorX
cd AnchorX
yarn install
After the dependencies are installed, you should be able to run the Android or iOS version with the following commands:
- iOS:
yarn run ios
- Android:
yarn run android
Once the application is running start a session by putting your favorite username. You will see a placeholder balance showing $1000
, your username
and stellarAccount
. In the following session you will install the JS Stellar SDK and use it to show the account's real balance.
Using the Stellar-SDK in React Native
Using the JS Stellar SDK requires extra setup because the package depends in some Node core modules like crypto
or globals like process
, which are not available in React Native.
If you try to run the application and import the SDK, you will see an error like the following:
This is a known issue in the React Native world and there is a solution for it. To solve the problem you need to install the package node-libs-react-native which provides the implementation for some of those modules and vm-browserify.
Run the following commands:
yarn add node-libs-react-native vm-browserify react-native-randombytes
react-native link
// rn-cli.config.js
const extraNodeModules = require('node-libs-react-native')
extraNodeModules.vm = require.resolve('vm-browserify')
module.exports = {
getTransformModulePath() {
return require.resolve("react-native-typescript-transformer");
},
getSourceExts() {
return ["ts", "tsx"];
},
extraNodeModules
};
Edit the file rn-cli.config.js
file in the root directory to look like the code in the right and then add to index.js
in the root container the following at the top: import 'node-libs-react-native/globals'
You can see the changes in pull request #10. The relevant pieces are:
Installing the SDK and writing your first component
Once you have the depencies to install the SDK, run the following command:
yarn add stellar-sdk @types/stellar-sdk
Next you need to create a new container component which will load the Stellar account from the ledger and then display the balance for AnchorX custom asset.
The container component will have the following props:
stellarAccount
: Stellar account id to load.asset
: since Stellar accounts can hold multiple assets, you'll tell explicitly to the component which asset to display.
import * as React from 'react'
import {
Platform,
StyleSheet,
View
} from 'react-native'
import {
Asset,
AccountResponse,
Network,
Server
} from 'stellar-sdk'
import { Text } from 'native-base'
import { styles as s } from 'react-native-style-tachyons'
import Loading from '../components/Loading'
interface Props {
accountId: string
asset: Asset
}
interface State {
sdkAccount: AccountResponse
}
export default class Balance extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {}
}
async componentDidMount() {
const { accountId } = this.props
const stellarServer = new Server('https://horizon-testnet.stellar.org')
// load the account from Horizon
const sdkAccount = await stellarServer.loadAccount(accountId)
this.setState({
sdkAccount
})
}
render() {
const { sdkAccount } = this.state
const { asset } = this.props
if (!sdkAccount) return <Loading />
const { balance } = sdkAccount.balances.find(({ asset_code, asset_issuer }) => asset_code === asset.code && asset_issuer === asset.issuer)
return (
<View style={[s.jcc, s.aic]}>
<Text style={s.f3}>
{balance}
</Text>
</View>
)
}
}
Create the component by running touch app/containers/Balance.tsx
and then paste the content on the right.
Update
app/containers/Home.tsx
to render the Balance component.
import { NavigationScreenProps } from 'react-navigation'
+import { Asset } from 'stellar-sdk'
+
+import Balance from './Balance'
export default class Home extends React.Component<NavigationScreenProps> {
static navigationOptions = {
@@ -18,6 +21,11 @@ export default class Home extends React.Component<NavigationScreenProps> {
}
render() {
+ const anchorXUSD = new Asset(
+ 'USD',
+ 'GBX67BEOABQAELIP2XTC6JXHJPASKYCIQNS7WF6GWPSCBEAJEK74HK36'
+ )
+
return (
<CurrentUserQuery query={GET_CURRENT_USER_QUERY}>
{({ loading, data }) => {
@@ -58,7 +66,9 @@ export default class Home extends React.Component<NavigationScreenProps> {
s.pa4
]}
>
- <Text>$1000</Text>
+ <Balance
+ accountId={data.me.stellarAccount}
+ asset={anchorXUSD} />
</View>
<View>
After that, you will find the changes in the Home
container to use Balance
.
If you run the app, you should see the balance in your AnchorX account.
You can see the changes in this section in pull request #11.
Balance updates
You can see your balance noew but if you send or receive USD, the balance won't update. The horizon server allows you to call some end-points in streaming mode using Server-Sent Events, account details is one of those end-points.
In this chapter you will learn how to use the streaming mode to update the account balance.
In the following GIF you can see how you need to refresh the app to see the balance updated.
mutation {
credit(amount: "10", username:"your-username") {
id
}
}
You can try it by going to https://anchorx-api.herokuapp.com/ and writing the mutation on the right using your username.
Using streaming mode
Edit
app/containers/Balance.tsx
async componentDidMount() {
const { accountId } = this.props
const stellarServer = new Server('https://horizon-testnet.stellar.org')
const sdkAccount = await stellarServer.loadAccount(accountId)
+ // Setup streaming to the accountId, this returns a function which
+ // you can use to close the stream.
+ let accountEventsClose = stellarServer.accounts().accountId(accountId).stream({
+ // onmessage is called each time the ledger closes
+ onmessage: res => {
+ const { sdkAccount } = this.state
+
+ // Check if balances changed and if they did update sdkAcount.balances
+ if (sdkAccount.balances !== res.balances) {
+ sdkAccount.balances = res.balances
+
+ this.setState({
+ sdkAccount: sdkAccount
+ })
+ }
+ }
+ });
+
+ // For convinience add this to the account so you can close
+ // on componentWillUnmount. hat-tip to StellarTerm ;)
+ sdkAccount.close = () => {
+ try {
+ accountEventsClose();
+ } catch(e) {
+ console.log('error closing account streaming')
+ }
+ }
+
this.setState({
sdkAccount
})
}
+ componentWillUnmount() {
+ const { sdkAccount } = this.state
+ // Close the stream when unmounting the component
+ sdkAccount.close()
+ }
+
render() {
const { sdkAccount } = this.state
const { asset } = this.props
}
The SDK uses the builder pattern to help you interact with horizon and its different functionalities. Once you have an instance of the server, you can setup streaming by calling the following command: stellarServer.accounts().accountId(accountId).stream({args})
.
You need to update the lifecycle methods componentDidMount
and componentWillUnmount
in app/containers/Balance.tsx
. On the right you will find the additions with comments.
In the GIF below you can see how the balance updates now after using streaming.
You can find the changes in this section in pull request #12.
Next, let's create display the account payments.
Payment component
First you need to add a component to display a single payment. If the payment if a debit it will show the message "You paid X" and the value. If you are the receipient, then it shows "X paid you".
The only thing concerning Stellar here is how you can use the TypeScript type to specify that the component will receive a PaymentOperationRecord
as part of the props.
import { PaymentOperationRecord } from 'stellar-sdk'
interface Props {
account: string
payment: PaymentOperationRecord
}
You can find the component implementation in pull request #13.
Next you'll write a container to load the payments and use the Payment component for each entry.
Showing payments
Create the file
app/containers/Payments.tsx
with the following code:
import * as React from 'react'
import { View, StyleSheet, FlatList } from 'react-native'
import { Container, Content, Text, Button, Spinner } from 'native-base'
import { styles as s } from 'react-native-style-tachyons'
import { Asset, PaymentOperationRecord, Server, Network } from 'stellar-sdk'
import Payment from '../components/Payment'
interface Props {
accountId: string
asset?: Asset
}
interface State {
loadMore: boolean
payments: PaymentOperationRecord[]
loading: boolean
closeStreaming?: any
}
export default class Payments extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
loadMore: true,
payments: [],
loading: false
}
}
async loadPayments(cursor?: string) {
const { accountId, asset } = this.props
const { payments } = this.state
Network.useTestNetwork()
const stellarServer = new Server('https://horizon-testnet.stellar.org')
// Load payments for account in descending order, most recentfirst.
let builder = stellarServer
.payments()
.forAccount(accountId)
.order('desc')
if (cursor) {
builder.cursor(cursor)
}
const { records } = await builder.call()
if (asset) {
return records.filter((payment) => payment.asset_code === asset.code && payment.asset_issuer === asset.issuer)
} else {
return records
}
}
/* Use `now` in the cursor to be notified of new payments. If you don't
* set the cursor to "now", then you'll be notified about all the
* payments since account's creation.*/
listenForPayments(cursor = 'now') {
const { closeStreaming } = this.state
if (closeStreaming) {
try {
closeStreaming()
} catch (e) {
console.log('error closing streaming')
}
}
const { accountId } = this.props
Network.useTestNetwork()
const server = new Server('https://horizon-testnet.stellar.org')
// Notice how you can use PaymentOperationRecord from @types/stellar-sdk
let handleMessage = (payment: PaymentOperationRecord) => {
const { asset } = this.props
const { payments } = this.state
if (payment.asset_code === asset.code && payment.asset_issuer === asset.issuer) {
this.setState({
payments: [payment, ...payments]
})
}
}
this.setState({
closeStreaming: server.payments()
.cursor(cursor)
.forAccount(accountId)
.stream({
onmessage: handleMessage
})
})
}
/* After the component mounts, load the first page of payments and
* setup streaming*/
async componentDidMount() {
const payments = await this.loadPayments()
this.setState({
payments
})
this.listenForPayments()
}
componentWillUnmount() {
const { closeStreaming } = this.state
try {
closeStreaming && closeStreaming()
} catch (e) {
console.log('error closing streaming')
}
}
/* Method use for pagination, it gets calls when the end of the list is
* reached. It calls the generic function loadPayments using the last
* item in payments as the cursor.
*/
async fetchMoreData() {
const { accountId } = this.props
const { payments } = this.state
const cursor = payments[payments.length - 1].id
this.setState({
loading: true
})
const nextPage = await this.loadPayments(cursor)
const state = {
loadMore: true,
payments: [...payments, ...nextPage],
loading: false
}
if (nextPage.length === 0) {
state.loadMore = false
}
this.setState(state)
}
render() {
const { accountId } = this.props
const { loadMore, payments, loading } = this.state
return (
<Container style={{ backgroundColor: '#F5FCFF' }} >
<FlatList
data={payments}
renderItem={({ item }) => <Payment key={item.id} payment={item} account={accountId} />}
keyExtractor={(item) => item.id}
onEndReachedThreshold={0.2}
onEndReached={({ distanceFromEnd }) => {
if (!loadMore || loading) {
return
}
return this.fetchMoreData()
}}
refreshing={loading}
ListFooterComponent={loading && <Spinner color="blue" />}
onRefresh={() => {
if (payments.length > 0) {
this.listenForPayments(payments[0].id)
}
}}
/>
</Container>
)
}
}
To show transactions, you will follow a similar pattern to the one use in the previous section. Create a container component which will load the last page of payments using the payments method in the Server class. And then use streaming from the PaymentCallBuilder.
At this point most of the changes are related with React and building components. On the right you can see the component implementation. The following functions use the SDK:
loadPayments
: Sets the server and loads the first page of payments.listenForPayments
: Set streaming.fetchMoreData
: Call when loading more payments.
Also, noticed how you can use types defined in @types/stellar-sdk
like PaymentOperationRecord
.
After creating the component, you can import it in the Home container and use it by passing the accountId
and asset
, you can see the change here.
You can see the the changes in pull request #14.
Depositing and withdrawing fiat from the wallet
In this section you will add two screens to deposit or withdraw money from AnchorX.
First you need to add a transfer form component which you will reuse for both operations.
In pull request #15 you can find the implementation for the component displayed in the GIF above. It takes as props
a function which call the mutation credit or debit. Next, you will find the implantation for both containers.
Deposits
The content below belongs to app/containers/Deposit.tsx
import * as React from 'react'
import { View } from 'react-native'
import { NavigationScreenProps } from 'react-navigation'
import { Body, Button, Container, Content, Header, Icon, Left, Right, Text, Title } from 'native-base'
import { styles as s } from 'react-native-style-tachyons'
import layoutStyles from '../styles/layout'
import DismissableStackNavigator from './DismissableStackNavigator'
import { Transaction } from '../Types'
import TransferForm from '../components/TransferForm'
import CreditDebitMutation, { CREDIT_MUTATION } from '../mutations/CreditDebit'
import CurrentUserQuery, { GET_CURRENT_USER_QUERY } from '../queries/CurrentUser'
export class DepositScreen extends React.Component<NavigationScreenProps> {
async deposit(mutation, username: string, amount: string): Promise<Transaction> {
const { data } = await mutation({
variables: {
username,
amount
}
})
return data.credit
}
didSend(): void {
this.props.navigation.navigate('Home')
}
render() {
const { navigation } = this.props
const userSelected = () => navigation.navigate('PaymentDetails')
return (
<CurrentUserQuery query={GET_CURRENT_USER_QUERY}>
{({ loading, data }) => {
if (loading) {
return <Loading />
}
const { me } = data
return (
<Container style={{backgroundColor: '#F5FCFF'}}>
<Header style={layoutStyles.header}>
<Left>
<Button
transparent
onPress={() => this.props.screenProps.dismiss()}>
<Icon name='close' />
</Button>
</Left>
<Body>
<Title>Deposit</Title>
</Body>
<Right />
</Header>
<Content scrollEnabled={false}>
<CreditDebitMutation mutation={CREDIT_MUTATION}>
{(mutation, { data }) => {
return (
<TransferForm send={this.deposit.bind(this, mutation, me.username)} didSend={this.didSend.bind(this)} />
)
}}
</CreditDebitMutation>
</Content>
</Container>
)
}}
</CurrentUserQuery>
)
}
}
export default DismissableStackNavigator(
{
Deposit: {
screen: DepositScreen
}
},
{
initialRouteName: 'Deposit',
headerMode: 'none',
cardStyle: {
backgroundColor: '#F5FCFF'
}
}
)
To deposit money into the account, you will create a new container which will use the deposit mutation and the transfer form. On the right you can see the implementation for such container. For this tutorial, assume your bank account has been linked when you went through signup.
Pull request #16 shows you all the implementation details. The following are some of the relevant changes:
- Mutation component: https://github.com/abuiles/AnchorX/pull/16/files#diff-edc1bb1b69c5a2a4ae142da5ad61e9c1
- Add a new option to the menu bar: https://github.com/abuiles/AnchorX/pull/16/files#diff-82d98b5838f0e7e19e05fe1a563eb307R34
- Extend
TransferForm
to receive adidSend
callback: https://github.com/abuiles/AnchorX/pull/16/files#diff-76291595d5a283ba48caa79aca053ac0L9
Next, you will add the withdrawals container.
Withdrawals
Withdrawals are very similar to deposits, the only thing that changes is the mututation used in the callback. Instead of using credit
you will use debit
.
Since the code is very similar you won't see it here, instead you can go to Github and check pull request #17 which includes lal the changes.
Next you will implement the final screen which is which will allow you to send money to other AnchorX users, using their username
.
Sending payments
To send new payments you need to create a payment form and then change the new payment container to render the form. The boilerplate has two screens, the first one is meant to be used for searching the recipient like in Venmo and the second one to input the amount, however, this tutorial won't include the search part and it would be left as an exercise to the reader as mapping the Stellar address to real usernames in the payments list. The payment form will take the recipient's username and the amount and then call the payment mutation.
New Payment form
import * as React from 'react';
import { Alert } from 'react-native'
import { Container, Content, Form, Item, Input, Label, Button, Text, Spinner } from 'native-base';
import { styles as s } from "react-native-style-tachyons";
import { Transaction } from '../Types'
interface Props {
send: (username: string, amount: string) => Promise<Transaction>
didSend: () => void
}
interface State {
amount: string
username: string
sending: boolean
}
class PaymentForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
amount: '',
username: '',
sending: false
}
}
async send(): Promise<Transaction> {
const { username, amount } = this.state
this.setState({
sending: true
})
const transaction = await this.props.send(username, amount)
this.setState({
sending: false
})
this.props.didSend()
return transaction
}
render() {
const { amount, username, sending } = this.state
return (
<Container style={{backgroundColor: '#F5FCFF'}}>
<Content scrollEnabled={false}>
<Form>
<Item floatingLabel>
<Label>Recipient username</Label>
<Input
onChangeText={(username) => this.setState({ username })}
value={username}
autoFocus={true}
autoCorrect={false}
autoCapitalize={"none"}
/>
</Item>
<Item floatingLabel>
<Label>Amount</Label>
<Input
onChangeText={(amount) => this.setState({ amount })}
value={amount}
keyboardType={'decimal-pad'}
autoCorrect={false}
autoCapitalize={"none"}
/>
</Item>
{!sending && (
<Button full style={[s.mt4]} onPress={() => this.send()}>
<Text>Send</Text>
</Button>
)}
{sending && <Spinner color="blue" />}
</Form>
</Content>
</Container>
);
}
}
export default PaymentForm
First you need to add a form to send new payments. The code on the right shows you the component used in the GIF below.
You can see the implementation in pull request #18. Next let's update the new payment container to use the payment form and add a new mutation for payments.
New Payment container
You new to use the payment form component in the new payment container. Also, you need to add a new mutation component for payment
. The following GIF shows the functionality working:
You can find the changes in pull request #19. The following are the relevant parts of that PR:
Mutation
: https://github.com/abuiles/AnchorX/pull/19/files#diff-e36e4c2d86d36183ccec7eb4f7090ee4PaymentForm
: https://github.com/abuiles/AnchorX/pull/19/files#diff-6b176d09842db5660d3df5ad191d4c67R69
Conclusion
Now you have a basic clone of Venmo using Stellar. In this section you learned how to build a mobile wallet using the JS Stellar SDK in React Native and how to use to do things like read an account from the ledger or use streaming. There are many more features that could be build but they will be left as an exercise to the reader. For example, you could add a resolver which takes a Stellar account and returns the username associated with that account.
As mentioned at the beginning of this tutorial, AnchorX takes a different approach to other anchors since it manages users account. You could try modifying this wallet to allow each user to manage their own private keys and then use AnchorX to resolver usernames to Stellar addresses.
Before finishing the tutorial, you will learn about some best practices around account management and security.
Best practices
Using multisignature
Transactions in Stellar need an authorization to be valid. Such
authorization is signed by the public key associated with the
account. However, Stellar allows you to add multiple signers to an
account and then specify a security level which can be low
, medium
or
high
. Each level has the following features:
low
: it allows to update the sequence number for the source account and the allow trust operation.medium
: all other operations listed herehigh
: it allows signers or the thresholds update and merge account operation.
The official documentation has a completed summary on multisignature https://www.stellar.org/developers/guides/concepts/multi-sig.html - please give it a read before continuing.
const signerKeys = Keypair.fromSecret(
// Use something like KMS in production
AES.decrypt(
sender.stellarSeed,
ENVCryptoSecret
).toString(enc.Utf8)
)
You can use multisignature to create different security schemas around account management, if you recall, you were decrypting the seed for processing new payments like displayed in the right. The issue with this is that you have to manage all your user keys and then if one gets exposed then you lose control of the account.
An alternative setting instead of using the master key as signer, is to add two more signers to each account with mid and high threshold, and change the master key to have a low threshold. With this you will have the key with the highest threshold always offline and put the mid-threshold key in a service which can not be easily compromised. The downside of doing this is that it that it will increate the minium balance per account.
This kind of schema was recently used by SatoshiPay in their Lumens give away, they were giving away lumens but you could only use them to pay creators in their platform. To force this, they put a multisignature schema in each account so if you were trying to send lumens to someone who was not a creator, they would not sign the transaction.
Let's look next at the code to implement multisignature.
Adding signers to an account
async function addSigners(keypair: Keypair) {
Network.useTestNetwork();
const stellarServer = new Server('https://horizon-testnet.stellar.org');
const paymentsId = 'GCQOZGXMH6MI3JKWWO365BCXUJN6MFZR7XMKKCHGDY2K6JNCYFRH6M4C'
const adminId = 'GA6KTSHWU6GX6ER6HTWMGONS4VLQ4ZHONU6RKBGXTT7HFLV6NHX3ONAL'
const account = await stellarServer.loadAccount(newAccountKeypair.publicKey())
var transaction = new TransactionBuilder(account)
.addOperation(
Operation.setOptions({
signer: {
ed25519PublicKey: paymentsId,
weight: 2
}
})
)
.addOperation(
Operation.setOptions({
signer: {
ed25519PublicKey: adminId,
weight: 5
}
}))
.addOperation(
Operation.setOptions({
masterWeight: 0, // set master key weight to 0, it can't do anything
lowThreshold: 1,
medThreshold: 2, // Allows paymentsId to send payments
highThreshold: 5 // Allows adminId to manage account
}))
.build();
transaction.sign(keypair)
const result = await server.submitTransaction(transaction)
}
Let's assume you want to use account
GCQOZGXMH6MI3JKWWO365BCXUJN6MFZR7XMKKCHGDY2K6JNCYFRH6M4C
for signing
payments and account
GA6KTSHWU6GX6ER6HTWMGONS4VLQ4ZHONU6RKBGXTT7HFLV6NHX3ONAL
to be the
admin of all your users accunts.
You will have to call the code on the right after creating the account in the ledger to change the signers schema.
Pull request #14 shows you how to apply the multisignature schema above to anchorx-api.
Managing the issuing account
After issuing AnchorX's custom asset, you continue using the issuing account to create and authorize who could hold your asset. There is a security risk involved with this since if the issuing account gets compromised the attackers can create as many USD as they want.
To manage this, you can follow the recommendation here which is to maintain two accounts, one for issuing and destroying assets and then a base account which will hold a huge amount of your asset so it can credit people who transfer USD from their bank account to their AnchorX account. That way you can keep the secret key for the issuing account offline.
Additionally, since you need the allowTrust
operation, the you can
add a signer with low threshold to the issuing account.
Stellar Core and Horizon
For testing purposes it's fine to relay on the public test network run by the SDF, however if you are building your own service, you should be running your own nodes and horizon instance. You can learn more about it in the getting started guide.
Conclusion
As mentioned at the beginning of the tutorial, the goal here was to show you a different approach to creating an anchor and at the same time help you get familiar with some concepts in Stellar. Don't take what I say here as written in stone, the requirements might be different for your particular situation, always do your research. There are other resources out there where you can go and ask questions like the Stellar community Slack and the Stellar Stack Exchange. If there is something else you would like me to write about please let me know. I'm looking forward to writing more about Stellar and helping you build awesome stuff on top of it.
--Fin